未发生C#WinForms应用程序中的预期跨线程exception
我来自WPF,我是WinForms的新手。 在研究跨线程情况时,我预计不会发生跨线程exception。
以下是我的情况的基本摘要。 有一个名为label1
Label
控件和一个名为button1
Button
。 button1
的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通常仅在您进行调试时才会发生。