未发生C#WinForms应用程序中的预期跨线程exception

我来自WPF,我是WinForms的新手。 在研究跨线程情况时,我预计不会发生跨线程exception。

以下是我的情况的基本摘要。 有一个名为label1 Label控件和一个名为button1 Buttonbutton1的click事件处理程序基本上如下所示:

 private void button1_Click(object sender, EventArgs e) { Task.Factory.StartNew(()=> { label1.Text = "Some-Other-New-Text"; }); } 

这不会像我期望的那样抛出跨线程exception。 这是因为WinForms应用程序没有交叉线程问题吗? 请注意,我在Visual Studio 2013以及Visual Studio 2010中对此进行了调查。

Windows窗体在Windows消息传递基础结构之上工作。 这意味着您对相关控件执行的许多操作实际上都委托给Windows以支持所有控件的正确本机行为。

Label不会更改默认实现,默认情况下它不会在托管代码中缓存文本。 这意味着它使用SetWindowText本机方法设置当前标签文本(相应地, GetWindowText读取它),它将WM_SETTEXT到消息循环。 真正的更新发生在处理消息循环的线程上,也称为UI线程。 除非你不顾一切地禁止这种调用(当前参考源中的Control.checkForIllegalCrossThreadCall ),否则它将起作用。 默认情况下,这是根据是否附加调试器来设置的 – 因此您的代码可能会在调试时崩溃,但是在调试器之外可以工作,因为SetWindowText恰好是线程安全的。 Text属性的其他部分可能是也可能不是线程安全的,但如果你很幸运,一切都运行正常。

您可以明确地将Control.CheckForIllegalCrossThreadCall设置为true ,我建议您这样做。 从多个线程访问任何资源都很容易调试问题,并且需要在UI上完成对UI线程所做的任何工作……无论如何都是UI线程的工作。

从UI线程专门操作UI会给您带来非常重要的好处:

  • 可预测性和可靠性 – 事情往往会发生在某些可靠的订单中。 如果我有一百个线程设置两个不同控件的Text ,则将UI更新委派给UI线程将确保两个控件始终具有一致的值,而直接从后台线程更新将倾向于“随机”交错更新。 在实际应用程序中,这可能会导致混淆,也很难发现错误。 请注意,这不是绝对的 – 任何await / Application.DoEvents可能会也可能不会破坏它。 但即使在这种情况下,您也有明确定义的同步点,而不是先发制人的多任务处理。
  • multithreading很难。 您使用的大多数内容都不是线程安全的,甚至据称线程安全操作可能会出现multithreading错误,或者在MT场景中运行时只会出现复杂的行为。 即使是在序列中更新两个布尔值这样的简单事情也会变得很危险,并且可能会引入难以调试的错误。 你最好的选择是保持尽可能多的线程仿真。 您通常会发现可以将线程之间的所有接口限制在代码的一小部分,这使得它们更容易进行测试和调试。
  • 无论如何,当你已经将“工作”与“UI”分开时,它真的很便宜。 这本身就是一个非常方便的设计实践。

作为旁注,您通常希望await任务,或明确处理exception。 如果启用Control.CheckForIllegalCrossThreadCall ,标签将不再更新,但它也不会显示exception – 默认情况下,线程池线程会忽略当前未处理的exception(自.NET 4.5.2 IIRC以来)。 await会将任何exception(和返回值)编组回UI线程。

在我的情况下,当从UI线程以外的线程访问ToolStripStatusLabel时,我期望WinForms应用程序中存在跨线程exception(在调试器†中 )。

我发现不发生跨线程exception的原因是因为ToolStripStatusLabel不是从System.Windows.Forms.Control派生的,因此没有CheckForIllegalCrossThreadCalls属性。

(我能够通过首先certificate尝试访问非UI线程中的TextBox 确实引发了跨线程exception来缩小范围。)


†请参阅此答案 ,了解为什么WinForms跨线程exception通常仅在您进行调试时才会发生。