我确信这有一个好的(或至少是体面的)原因。它是什么?
答案 0 :(得分:24)
我认为这是一个很棒的问题 - 我认为需要更好的 答案。
当然唯一的原因就是那里 在某个框架中某种东西 这不是非常线程安全的。
在System.Windows.Forms的每个控件上,“something”几乎就是每个实例成员。
System.Windows.Forms中许多控件的MSDN文档,如果不是全部,请说“此类型的任何公共静态(在Visual Basic中为Shared)成员都是线程安全的。不保证任何实例成员是线程安全的。“
这意味着TextBox.Text {get; set;}
等实例成员不是 可重入 。
使每个实例成员都是线程安全的,可能会引入大量应用程序不需要的开销。相反,.Net框架的设计者决定,并且我认为正确的是,应该将来自多个线程的表单控件的访问同步的负担放在程序员身上。
[编辑]
虽然这个问题只是问“为什么”这里是指向解释“如何”的文章的链接:
如何:在MSDN上对Windows窗体控件进行线程安全调用
答案 1 :(得分:10)
因为您可以轻松地结束僵局(以及其他问题)。
例如,您的辅助线程可能正在尝试更新UI控件,但UI控件将等待由辅助线程锁定的资源被释放,因此两个线程最终都会等待彼此完成。正如其他人所评论的那样,这种情况并不是UI代码所特有的,但却特别常见。
在其他语言(如C ++)中,您可以自由尝试执行此操作(没有像在WinForms中那样抛出异常),但是如果发生死锁,您的应用程序可能会冻结并停止响应。
顺便提一下,您可以轻松告诉UI线程您要更新控件,只需创建一个委托,然后在该控件上调用(异步)BeginInvoke方法,将其传递给您的委托。 E.g。
myControl.BeginInvoke(myControl.UpdateFunction);
这相当于从工作线程
执行C ++ / MFC PostMessage答案 2 :(得分:7)
虽然听起来合理,但约翰的回答并不正确。事实上即使使用Invoke,你仍然不安全,不会遇到死锁情况。当使用Invoke处理在后台线程上触发的事件时,甚至可能导致此问题。
真正的原因更多地与竞争条件有关,并在古代的Win32时代奠定了基础。我无法解释这里的细节,关键字是消息泵,WM_PAINT事件以及“SEND”和“POST”之间的细微差别。
答案 3 :(得分:2)
回到1.0 / 1.1在调试期间没有抛出任何异常,而是你得到的是一个间歇性的运行时挂起场景。太好了! :) 因此,使用2.0,他们使这个场景抛出一个异常,并且非常正确。
实际原因可能是(正如Adam Haile所述)某种并发/锁定问题。 请注意,正常的.NET API(例如TextBox.Text =“Hello”;)包装SEND命令(需要立即执行操作),如果在执行更新操作的线程上执行单独的线程,则会产生问题。使用Invoke / BeginInvoke使用POST来代替行动。
有关SEND和POST here的更多信息。
答案 4 :(得分:1)
这样你就没有两件事情试图同时更新控件。 (如果CPU在写/读中间切换到另一个线程,则可能发生这种情况) 在多个线程之间访问共享变量时,需要使用互斥锁(或其他一些同步)的原因相同。
编辑:
在其他语言中,例如C ++ 自由尝试这样做(没有 异常被抛出 WinForms),但你最终会学习 艰难的方式!
啊,是的......我在C / C ++和C#之间切换,因此比我应该更加通用,对不起......他是对的,你可以做到这一点C / C ++,但它会回来咬你!
答案 5 :(得分:1)
还需要在对同时调用敏感的更新函数中实现同步。对UI元素执行此操作在应用程序和操作系统级别上都会很昂贵,而对于绝大多数代码来说都是完全冗余的。
某些API提供了一种更改系统当前线程所有权的方法,因此您可以临时(或永久)从其他线程更新系统,而无需借助线程间通信。
答案 6 :(得分:-1)
嗯我不太确定,但我认为当我们有一个进度控件,比如等待条,进度条,我们可以从另一个线程更新它们的值,一切都很好,没有任何故障。