【问题标题】:Expected cross-thread exception in a C# WinForms application did not occurC# WinForms 应用程序中未发生预期的跨线程异常
【发布时间】:2016-04-26 09:18:42
【问题描述】:

我来自 WPF,我是 WinForms 的新手。在调查跨线程情况时,没有发生我预期的跨线程异常。

这是我的情况的基本总结。有一个名为label1Label 控件和一个名为button1Buttonbutton1 的点击事件处理程序基本上如下所示:

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

这并没有像我期望的那样引发跨线程异常。这是因为 WinForms 应用程序没有跨线程问题吗?请注意,我在 Visual Studio 2013 和 Visual Studio 2010 中对此进行了调查。

【问题讨论】:

  • @有时有效,有时无效。跨线程异常不一致。
  • 也许 Control.CheckForIllegalCrossThreadCalls 被设置为 false
  • @WonHyoungLee 不确定我是否理解正确。如果标志为假,那么它当然可以正常工作,并且标签会更改。如果标志是true,程序检查非法的跨线程调用,唯一奇怪的是异常杀死线程而没有在任何地方冒泡。
  • 我也很惊讶它(有时?)默认情况下是错误的。我强烈建议将其设置为 true 并确保仅从 ui 线程更新 ui 元素。
  • @RenéVogt 根据最新的参考源默认设置为Debugger.IsAttached。所以它在调试器中崩溃,但在调试器之外“工作”。

标签: c# .net multithreading winforms exception


【解决方案1】:

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 可能会或可能不会破坏这一点。但即使在这种情况下,您也有明确定义的同步点,而不是抢占式多任务处理。
  • 多线程很难。您使用的大多数东西都不是线程安全的,甚至据称线程安全的操作在 MT 场景中运行时也可能存在多线程错误或简单的复杂行为。即使是像更新序列中的两个布尔值这样简单的事情也会变得危险,并且可能会引入难以调试的错误。你最好的选择是尽可能多地保持线程仿射。您通常会发现可以将线程之间的所有接口限制在代码的一小部分,这使得它们更容易测试和调试。
  • 无论如何,当您已经将“工作”与“用户界面”分开时,它真的很便宜。这本身就是一种非常方便的设计实践。

作为旁注,您通常希望await 分拆的任务,或显式处理异常。如果启用Control.CheckForIllegalCrossThreadCall,标签将不再更新,但也不会显示异常 - 默认情况下,线程池线程现在忽略未处理的异常(自 .NET 4.5.2 IIRC 起)。 await 会将所有异常(和返回值)编组回 UI 线程。

【讨论】:

    【解决方案2】:

    在我的情况下,当从 UI 线程以外的线程访问 ToolStripStatusLabel 时,我预计我的 WinForms 应用程序中会出现跨线程异常(在调试器中)。

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

    (我能够通过首先证明尝试在非 UI 线程中访问 TextBox 来缩小范围确实引发了跨线程异常。)


    † 请参阅this answer,了解 WinForms 跨线程异常通常仅在您调试时发生的原因。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-19
      • 1970-01-01
      相关资源
      最近更新 更多