C#WinForms应用程序中未发生预期的跨线程异常 [英] Expected cross-thread exception in a C# WinForms application did not occur

查看:146
本文介绍了C#WinForms应用程序中未发生预期的跨线程异常的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm coming from WPF and I am new to WinForms. While investigating a cross-threading situation, a cross-thread exception that I expected did not occur.

这是我的情况的基本摘要.有一个名为label1Label控件和一个名为button1Button控件. button1的click事件处理程序基本上看起来像这样:

Here is a basic summary of my situation. There is one Label control named label1 and one Button named button1. The click event handler for button1 essentially looks like this:

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

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

This is not throwing a cross-thread exception as I expect it to. Is this because WinForms applications do not have cross-threading problems? Note that I have investigated this in Visual Studio 2013 as well as in Visual Studio 2010.

推荐答案

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

Windows forms work on top of the Windows messaging infrastructure. That means that a lot of the operations you perform on the controls in question are actually delegated to Windows to support proper native behaviour of all the controls.

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

Label doesn't change the default implementation, and it doesn't cache the text in managed code by default. This means that it uses the SetWindowText native method to set the current label text (and correspondingly, GetWindowText to read it), which posts a WM_SETTEXT to the message loop. The real update happens on the thread that handles the message loop, also known as the UI thread. Unless you go out of your way to prohibit this kind of call (Control.checkForIllegalCrossThreadCall in current reference source), it will work. By default, this is set depending on whether a debugger is attached - so your code may crash while debugging, but will work outside of a debugger, since SetWindowText happens to be thread-safe. There's other parts of the Text property that may or may not be thread-safe, but if you're lucky, everything works just fine.

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

You can set Control.CheckForIllegalCrossThreadCall to true explicitly, and I'd recommend you to do so. Accessing any resource from multiple threads is prone to hard to debug issues, and marshalling whatever work needs to be done on the UI to the UI thread is... kind of the job of the UI thread anyway.

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

Manipulating the UI exclusively from the UI thread gives you quite important benefits:

  • 可预测性和可实现性-事情往往会以某些可靠的顺序发生.如果我有一百个线程设置两个不同控件的Text,则将UI更新委派给UI线程将确保两个控件始终具有一致的值,而直接从后台线程进行更新将趋向于随机地交叉更新" ".在实际的应用程序中,这可能导致混乱以及难以发现错误.请注意,这不是绝对的-任何await/Application.DoEvents可能会也可能不会干扰这一点.但是即使在那种情况下,您也可以定义良好的同步点,而不是预先的多任务处理.
  • 多线程很难.您使用的大多数设备都不是线程安全的,甚至据说在MT场景中运行时,线程安全的操作可能也具有多线程错误或简单的复杂行为.即使像更新一个序列中的两个布尔值之类的简单事情也很危险,并且可能会引入难以调试的错误.最好的选择是保持尽可能多的线程仿射.通常,您会发现可以将线程之间的所有接口限制在代码的一小部分,这使得它们更易于测试和调试.
  • 当您已经将工作"与"UI"分开时,它确实很便宜.这本身就是一个非常方便的设计实践.
  • Predictability and realiability - things will tend to happen in certain reliable orders. If I have a hundred threads setting the Textof two different controls, delegating the UI update to the UI thread will ensure that the two controls always have consistent values, while updating directly from the background threads will tend to interleave the updates "randomly". In a real application, this can cause confusion as well as hard to find bugs. Note that this isn't absolute - any await/Application.DoEvents may or may not disrupt this. But even in that case, you have well defined synchronization points, rather than preëmptive multi-tasking.
  • Multi-threading is hard. Most things you work with aren't thread-safe, and even allegedly thread-safe operations may have multi-threading bugs or simply complicated behaviour when running in a MT scenario. Even a simple thing like updating two booleans in a sequence becomes dangerous, and may introduce hard to debug bugs. Your best bet is to keep as many things thread-affine as you can. You'll usually find you can confine all interfaces between the threads to a tiny portion of your code, which makes them much easier to test and debug.
  • It's really cheap when you've already separated "work" from "UI" anyway. And that's a pretty handy design practice on its own.

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

As a side-note, you usually want to await tasks that you spin off, or handle exceptions explicitly. If you enable Control.CheckForIllegalCrossThreadCall, the label will no longer update, but it will not show an exception either - by default, threadpool threads ignore unhandled exceptions nowadays (since .NET 4.5.2 IIRC). The await will marshall any exceptions (and return values) back to the UI thread.

这篇关于C#WinForms应用程序中未发生预期的跨线程异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆