【发布时间】:2010-12-27 16:47:08
【问题描述】:
我有一个ManualResetEvent。有一次,我使用WaitOne 等待该事件。令我惊讶的是,我在WaitOne 中收到了OnPaint 事件。这种情况也经常发生。
堆栈跟踪如下所示:
我知道WaitOne 会阻塞当前线程,并且在事件触发之前不允许执行任何其他代码。
有人能解释一下这里发生了什么吗?
【问题讨论】:
我有一个ManualResetEvent。有一次,我使用WaitOne 等待该事件。令我惊讶的是,我在WaitOne 中收到了OnPaint 事件。这种情况也经常发生。
堆栈跟踪如下所示:
我知道WaitOne 会阻塞当前线程,并且在事件触发之前不允许执行任何其他代码。
有人能解释一下这里发生了什么吗?
【问题讨论】:
这是设计使然。 CLR 遵守单线程单元 (STA) 的合同。 GUI 应用程序的主线程是 STA,这在 Windows 编程中是必需的,Main() 方法上的 [STAThread] 属性确保了这一点。
STA 线程的硬性规则是它必须泵送消息循环(如 Application.Run)并且永远不能阻塞。当后台线程使用任何 COM 单元线程对象时,阻塞 STA 线程很可能会导致死锁。其中有很多,剪贴板和 WebBrowser 是您在 .NET 程序中会遇到的常见工具。还有许多不太明显的,可作为 .NET 包装类使用。
当您使用 lock 语句或调用同步类的 Wait 方法时,CLR 通过泵送消息循环来确保阻塞不会导致死锁。或 Thread.Join()。该消息循环分派 WM_PAINT 消息,导致 Paint 事件运行。
您需要重新构建程序以确保这不会导致问题。专注于不阻塞主线程非常重要。例如,当您有 BackgroundWorker 类或 Control.BeginInvoke() 可供您使用时,很少需要它。出于某种奇怪的原因, Mutex 类不进行这种抽水,这可能是另一种方式。虽然如果你这样做,僵局就会潜伏在拐角处。
【讨论】:
Mutex 类是否有帮助。如果您有其他建议,我们当然欢迎。
Form1 我在 ManualResetEvent 上等待,Form2 有一个按钮来发出事件信号。我看到的是 WM_PAINT 事件正在发生,但我无法与被阻止的表单交互。此外,BeginInvoke 没有通过。你知道我怎样才能知道哪些消息会通过,哪些消息不会通过吗?
对于lock() 语句,我也看到了这种行为。显然,.net 框架线程类在等待 UI 线程上的锁定时会启动一个消息循环。这只是解释了正在发生的事情。原因可能是在使用旧版 STA COM 对象时防止死锁。我不知道有什么方法可以防止这种情况发生。
【讨论】: