关于多线程:在WinForms中,为什么不能从其他线程更新UI控件?

关于多线程:在WinForms中,为什么不能从其他线程更新UI控件?

In WinForms, why can't you update UI controls from other threads?

我敢肯定有一个很好的理由(或至少是体面的理由)。 它是什么?


I think this is a brilliant question -
and I think there is need of a better
answer.

Surely the only reason is that there
is something in a framework somewhere
that isn't very thread-safe.

该"内容"几乎是System.Windows.Forms中每个控件上的每个实例实例成员。

有关System.Windows.Forms中许多控件的MSDN文档(如果不是全部),请说"此类型的任何公共静态(Visual Basic中为Shared)成员都是线程安全的。不能保证任何实例成员都是线程安全的。"

这意味着实例成员(例如TextBox.Text {get; set;})不可重入。

使这些实例成员中的每个线程都安全可以导致大多数应用程序不需要的大量开销。而是.NET框架的设计者决定,并且我正确地认为,同步从多个线程访问表单控件的负担应该由程序员承担。

[编辑]

尽管此问题仅问"为什么",但是这里有指向解释"如何"的文章的链接:

如何:在MSDN上对Windows窗体控件进行线程安全调用

http://msdn.microsoft.com/en-us/library/ms171728.aspx


因为您很容易陷入僵局(还有其他问题)。

例如,您的辅助线程可能正在尝试更新UI控件,但是UI控件将等待由辅助线程锁定的资源被释放,因此两个线程最终都互相等待。正如其他人所评论的那样,这种情况并非UI代码独有,而是特别常见。

在其他语言(例如C ++)中,您可以随意尝试执行此操作(不会像WinForms中那样引发异常),但是如果发生死锁,您的应用程序可能会冻结并停止响应。

顺便说一句,您可以轻松地告诉UI线程您要更新控件,只需创建一个委托,然后在该控件上调用(异步)BeginInvoke方法即可将其传递给您的委托。例如。

1
myControl.BeginInvoke(myControl.UpdateFunction);

这等效于从工作线程执行C ++ / MFC PostMessage


尽管听起来合理,但约翰斯的回答并不正确。 实际上,即使使用Invoke,也无法避免陷入僵局。 使用Invoke处理在后台线程上触发的事件时,甚至可能导致此问题。

真正的原因与比赛条件有关,并且可以追溯到古代的Win32时代。 我在这里无法解释细节,关键字是消息泵,WM_PAINT事件以及" SEND"和" POST"之间的细微差别。

可以在此处和此处找到更多信息。


回到1.0 / 1.1,在调试过程中没有引发异常,而是出现了间歇性的运行时挂起情况。真好! :)
因此,在2.0版本中,他们使这种情况引发了异常,这是正确的。

造成这种情况的实际原因可能是(如亚当·海尔所言)某种并发/锁问题。
请注意,普通的.NET api(例如TextBox.Text =" Hello";)包装了SEND命令(需要立即采取措施),这些命令如果在与执行更新操作不同的线程上执行,则可能会产生问题。使用Invoke / BeginInvoke使用POST代替,该POST将操作排队。

有关发送和发布的更多信息,请点击此处。


还需要在对同时调用敏感的更新功能内实现同步。对UI元素执行此操作在应用程序和OS级别上都将是昂贵的,并且对于绝大多数代码而言是完全多余的。

一些API提供了一种更改系统当前线程所有权的方法,因此您可以临时(或永久)从其他线程更新系统,而无需诉诸线程间通信。


这样一来,您就不会有两件事试图同时更新控件。 (如果CPU在写/读中间切换到另一个线程,则可能发生这种情况)
同样的原因,访问多个线程之间的共享变量时,需要使用互斥锁(或其他同步)。

编辑:

In other languages such as C++ you are
free to try and do this (without an
exception being thrown as in
WinForms), but you'll end up learning
the hard way!

嗯,是的。。。我在C / C ++和C#之间切换,因此本来应该更通用一些,对不起...他是正确的,您可以在C / C ++中执行此操作,但是它将返回到咬你!


嗯,我不太确定,但我认为,当我们拥有诸如等待条,进度条之类的进度控件时,我们可以从另一个线程更新其值,并且一切正常,没有任何故障。


推荐阅读