如何让System.Windows.Forms.Timer调用的方法等待另一个Timer方法执行完成?
让System.Windows.Forms.Timer方法等待System.Timers.Timer方法完成的解决方案
当然可以实现这个需求!不过得先注意两个计时器的线程差异:System.Timers.Timer的事件默认在ThreadPool后台线程执行,而System.Windows.Forms.Timer的Tick事件始终在UI线程执行。直接阻塞等待很容易导致UI假死,所以得用异步同步的方式来处理。
先看看你现有代码的问题:目前你是交替启停两个计时器,但如果fileRead()耗时超过1.5秒,aTimer的Elapsed事件可能在fileRead()还没完成时就触发,根本没做到“等待前者完成”的效果。而且两个计时器独立计时的逻辑,本来就容易出现不同步的情况。
下面给你两种可行的实现方案:
方案一:用TaskCompletionSource跟踪执行状态(推荐)
这个方案用TaskCompletionSource来标记fileRead()的完成状态,让prepareData()异步等待它完成,同时避免阻塞UI线程。
步骤1:添加全局变量和初始化计时器
先定义用于同步的变量,并把计时器设为单次触发(避免重复触发):
private TaskCompletionSource<bool> _fileReadCompletion; private readonly TimeSpan _interval = TimeSpan.FromSeconds(1.5); private System.Timers.Timer aTimer; private System.Windows.Forms.Timer myTimer; private readonly object _graphLock = new object(); // 保证graphList线程安全 // 初始化计时器的代码 private void InitTimers() { // 配置System.Timers.Timer aTimer = new System.Timers.Timer(_interval.TotalMilliseconds); aTimer.AutoReset = false; // 单次触发,避免重复执行 aTimer.Elapsed += prepData; // 配置WinForms Timer myTimer = new System.Windows.Forms.Timer(); myTimer.Interval = (int)_interval.TotalMilliseconds; myTimer.Tick += FileReadFunction; }
步骤2:修改FileReadFunction方法
把fileRead()放到后台线程执行,完成后标记状态并启动下一个计时器:
private async void FileReadFunction(object sender, EventArgs e) { myTimer.Stop(); _fileReadCompletion = new TaskCompletionSource<bool>(); try { // 后台执行fileRead,不阻塞UI await Task.Run(() => { lock (_graphLock) // 线程安全访问graphList { foreach (NetPinger.source.AddGraph b in graphList) { b.fileRead(); } } }); } catch (Exception ex) { MessageBox.Show(ex.ToString()); } finally { _fileReadCompletion.SetResult(true); // 标记fileRead完成 aTimer.Start(); // 启动aTimer执行prepareData } }
步骤3:修改prepData方法
异步等待fileRead()完成后再执行,最后延迟指定间隔重启WinForms Timer:
public async void prepData(object objectInfo, EventArgs e) { aTimer.Stop(); // 等待fileRead执行完成 if (_fileReadCompletion != null) { await _fileReadCompletion.Task; } try { lock (_graphLock) // 线程安全访问graphList { foreach (NetPinger.source.AddGraph b in graphList) { b.prepareData(); } } } finally { // 等待1.5秒后重启myTimer,保持原间隔逻辑 await Task.Delay(_interval); myTimer.Start(); } }
方案二:用ManualResetEventSlim同步(适合熟悉传统同步机制的场景)
如果你更习惯用传统的同步信号量,可以用ManualResetEventSlim,但同样要结合异步等待避免阻塞UI:
private ManualResetEventSlim _fileReadDone = new ManualResetEventSlim(false); // 修改FileReadFunction private async void FileReadFunction(object sender, EventArgs e) { myTimer.Stop(); _fileReadDone.Reset(); try { await Task.Run(() => { lock (_graphLock) { foreach (NetPinger.source.AddGraph b in graphList) { b.fileRead(); } } _fileReadDone.Set(); // 发出完成信号 }); } catch (Exception ex) { MessageBox.Show(ex.ToString()); _fileReadDone.Set(); // 异常时也要释放信号,避免死等 } finally { aTimer.Start(); } } // 修改prepData public async void prepData(object objectInfo, EventArgs e) { aTimer.Stop(); // 异步等待信号量,不阻塞线程 await Task.Run(() => _fileReadDone.Wait()); try { lock (_graphLock) { foreach (NetPinger.source.AddGraph b in graphList) { b.prepareData(); } } } finally { await Task.Delay(_interval); myTimer.Start(); } }
关键注意点
- 线程安全:因为
fileRead()在后台线程,prepareData()在UI线程,两者都访问graphList,一定要加锁避免并发问题。 - 避免UI阻塞:所有耗时操作都放到
Task.Run()里,用await异步等待,绝不能在UI线程调用Wait()或Thread.Sleep()。 - 计时器配置:把
System.Timers.Timer的AutoReset设为false,确保每次启动只触发一次事件,避免重复执行。
内容的提问来源于stack exchange,提问作者C.User
相关产品推荐
相关产品推荐

