如何在C#中使用Timer统计质数计算的耗时?
问题分析与解决方案
你的问题出在UI线程被完全阻塞了。Windows Forms的Timer控件是依赖UI消息队列触发Tick事件的,当你在button1_Click里执行那个耗时的双重质数计算循环时,UI线程全程忙着做计算,根本没机会处理Timer的Tick消息——也就是说timer1_Tick方法从头到尾都没被调用过,time变量自然一直是0。
如果一定要用Timer来实现耗时统计,核心思路是不能让计算任务阻塞UI线程,得把计算放到后台线程执行,这样UI线程才能正常响应Timer的事件。下面给你两种可行的实现方案:
方案1:用BackgroundWorker后台执行计算(WinForms推荐)
BackgroundWorker是WinForms专门为后台任务设计的组件,自带线程安全的进度和完成通知,很适合这种场景:
- 给窗体添加一个
BackgroundWorker控件(命名为backgroundWorker1),在属性面板开启WorkerReportsProgress和WorkerSupportsCancellation; - 替换原按钮点击事件和新增后台任务逻辑:
private double time = 0; private void button1_Click(object sender, EventArgs e) { time = 0; timer1.Start(); // 启动后台计算任务 backgroundWorker1.RunWorkerAsync(); } private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { bool isPrime = true; // 这里优化了质数判断逻辑:内层循环只需要到sqrt(i),原代码效率极低 for (int i = 2; i <= 200000; i++) { isPrime = true; for (int j = 2; j <= Math.Sqrt(i); j++) { if (i % j == 0) { isPrime = false; break; } } // 如果需要收集质数,可以存在List里,之后在完成事件更新UI // if (isPrime) ((List<int>)e.Result).Add(i); } } private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { timer1.Stop(); MessageBox.Show(time.ToString("F3") + "ms"); // 若收集了质数,在这里安全更新ListBox // if (e.Result is List<int> primes) // { // foreach (var prime in primes) listBox1.Items.Add(prime); // } } private void timer1_Tick(object sender, EventArgs e) { time += 0.001; }
方案2:用System.Timers.Timer(独立线程计时器)
这种Timer在独立线程触发事件,不受UI线程阻塞影响,但更新UI时需要手动Invoke到UI线程:
private double time = 0; private System.Timers.Timer countTimer; private bool isCalculating = false; private void button1_Click(object sender, EventArgs e) { time = 0; isCalculating = true; // 初始化1ms间隔的计时器 countTimer = new System.Timers.Timer(1); countTimer.Elapsed += CountTimer_Elapsed; countTimer.Start(); // 用Task启动后台计算 Task.Run(() => { bool isPrime = true; for (int i = 2; i <= 200000; i++) { isPrime = true; for (int j = 2; j <= Math.Sqrt(i); j++) { if (i % j == 0) { isPrime = false; break; } } } // 计算完成后停止计时器并更新UI isCalculating = false; countTimer.Stop(); this.Invoke((Action)(() => { MessageBox.Show(time.ToString("F3") + "ms"); })); }); } private void CountTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { if (isCalculating) time += 0.001; }
额外优化提示
原代码的质数判断效率极低:内层循环不需要遍历到200000,只需要到Math.Sqrt(i)就足够了——如果i有大于平方根的因数,对应的另一个因数必然小于平方根,这样能把计算时间缩短几个数量级。
最后补充一句:虽然Timer可以实现耗时统计,但Stopwatch是更专业、精度更高的选择(基于系统高精度计时器),Timer的间隔受系统调度影响,精度会有误差。
内容的提问来源于stack exchange,提问作者camseats
相关产品推荐
相关产品推荐

