使用EventWaitHandle与Dispatcher.BeginInvoke调度的线程同步异常问询
问题根源分析:UI异步调用的竞态,而非EventWaitHandle的问题
哈哈,这个坑我之前也踩过!首先得澄清:EventWaitHandle本身的同步逻辑完全没问题,你遇到的问题是因为工作线程(t00)的写入操作和后续修改共享变量的操作之间,存在隐形的竞态条件——大概率是你用了异步的UI调用(比如BeginInvoke)导致的。
先还原你大概率的代码场景
假设你的核心逻辑大概是这样(完全符合你描述的现象):
private string _sharedStr = "y"; private EventWaitHandle _evt = new AutoResetEvent(false); void LaunchThreads() { Thread t00 = new Thread(WriteToTextBoxY); Thread t01 = new Thread(WriteToTextBoxX); t00.Start(); t01.Start(); } void WriteToTextBoxY() { for (int i = 0; i < 20; i++) { // 重点:如果用的是BeginInvoke(异步投递UI请求) textBox1.BeginInvoke(new Action(() => textBox1.Text += _sharedStr)); } _sharedStr = "x"; // 修改共享变量 _evt.Set(); // 通知t01启动 } void WriteToTextBoxX() { _evt.WaitOne(); for (int i = 0; i < 20; i++) { textBox2.Invoke(new Action(() => textBox2.Text += _sharedStr)); } }
为什么会出现19次y+1次x?
当t00执行最后一次BeginInvoke时,它只是把“写入文本框”的请求丢进UI线程的消息队列,然后不等UI线程执行这个请求,就直接继续跑后面的代码:把_sharedStr改成x,再触发Event通知t01。
等UI线程终于轮到处理最后一次写入请求时,_sharedStr已经被改成x了——所以最后一次写入的自然是x,就出现了你看到的19次y+1次x的现象。
为什么加Thread.Sleep()能解决?
加Sleep相当于给了UI线程一段“缓冲时间”,让它来得及把t00投递的所有20次写入请求都处理完毕,之后t00才会修改_sharedStr。这时候最后一次写入已经用的是修改前的y,自然就正常了。
正确的解决方式(不要依赖Sleep这种临时hack)
Sleep只是凑活的 workaround,推荐两种靠谱的解法:
- 把异步调用换成同步调用:用
Invoke替代BeginInvoke。Invoke会阻塞工作线程,直到UI线程完成写入操作,再继续执行后续代码。这样最后一次写入完成后,才会修改_sharedStr,完全避免竞态。 - 等待所有异步写入请求完成:如果必须用异步调用,可以用
Task包装每个写入操作,然后等待所有任务完成后再修改变量:
async void WriteToTextBoxY() { var writeTasks = new List<Task>(); for (int i = 0; i < 20; i++) { // 用Task包装Invoke,收集所有任务 writeTasks.Add(Task.Run(() => textBox1.Invoke(() => textBox1.Text += _sharedStr))); } await Task.WhenAll(writeTasks); // 等待所有写入完成 _sharedStr = "x"; _evt.Set(); }
内容的提问来源于stack exchange,提问作者KMC
相关产品推荐
相关产品推荐

