18

我确信这有一个很好的(或至少体面的)理由。它是什么?

4

7 回答 7

25

我认为这是一个绝妙的问题——而且我认为需要一个更好的答案。

当然,唯一的原因是框架中的某个地方不是非常线程安全的。

这个“东西”几乎是 System.Windows.Forms 中每个控件上的每个实例成员。

System.Windows.Forms 中的许多控件(如果不是全部)的 MSDN 文档说 “这种类型的任何公共静态(在 Visual Basic 中共享)成员都是线程安全的。不保证任何实例成员都是线程安全的。”

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

使这些实例成员中的每一个都成为线程安全的可能会引入大多数应用程序不需要的大量开销。相反,.Net 框架的设计者决定,而且我认为是正确的,应该把从多个线程同步访问表单控件的负担放在程序员身上。

[编辑]

虽然这个问题只问“为什么”,但这里有一个解释“如何”的文章的链接:

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

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

于 2008-08-13T22:34:57.083 回答
9

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

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

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

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

myControl.BeginInvoke(myControl.UpdateFunction);

这相当于从工作线程执行 C++/MFC PostMessage

于 2008-08-13T20:47:12.760 回答
7

尽管听起来很合理,但约翰斯的回答并不正确。事实上,即使在使用 Invoke 时,如果不遇到死锁情况,您仍然不安全。在使用 Invoke 处理在后台线程上触发的事件时,甚至可能会导致此问题。


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


更多信息可以在这里这里找到。

于 2008-08-13T23:09:03.443 回答
2

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

造成这种情况的实际原因可能是(正如 Adam Haile 所说)某种并发/锁定问题。请注意,普通的 .NET api(例如 TextBox.Text = "Hello";)包装了 SEND 命令(需要立即采取行动),如果在执行更新的线程之外的单独线程上执行这些命令,则会产生问题。使用 Invoke/BeginInvoke 使用 POST 而不是将操作排队。

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

于 2008-08-13T20:52:21.190 回答
1

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

编辑:

在 C++ 等其他语言中,您可以自由地尝试这样做(不会像在 WinForms 中那样抛出异常),但您最终会以艰难的方式学习!

啊,是的...我在 C/C++ 和 C# 之间切换,因此比我应该的更通用一点,抱歉...他是对的,你可以在 C/C++ 中做到这一点,但它会回到咬你!

于 2008-08-13T20:44:01.473 回答
1

还需要在对同时调用敏感的更新函数中实现同步。对 UI 元素执行此操作在应用程序和操作系统级别都将是昂贵的,并且对于绝大多数代码来说完全是多余的。

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

于 2008-08-13T21:26:44.617 回答
-1

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

于 2008-10-13T13:44:54.790 回答