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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10349/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 16:36:28  来源:igfitidea点击:

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

winformsmultithreading

提问by Larsenal

I'm sure there is a good (or at least decent) reason for this. What is it?

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

采纳答案by John Sibly

Because you can easily end up with a deadlock (among other issues).

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

For exmaple, your secondary thread could be trying to update the UI control, but the UI control will be waiting for a resource locked by the secondary thread to be released, so both threads end up waiting for each other to finish. As others have commented this situation is not unique to UI code, but is particularly common.

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

In other languages such as C++ you are free to try and do this (without an exception being thrown as in WinForms), but your application may freeze and stop responding should a deadlock occur.

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

Incidentally, you can easily tell the UI thread that you want to update a control, just create a delegate, then call the (asynchronous) BeginInvoke method on that control passing it your delegate. E.g.

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

myControl.BeginInvoke(myControl.UpdateFunction);

This is the equivalent to doing a C++/MFC PostMessage from a worker thread

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

回答by Brian Ensink

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.

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

当然,唯一的原因是框架中某处不是线程安全的。

That "something" is almost every single instance member on every single control in System.Windows.Forms.

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

The MSDN documentation for many controls in System.Windows.Forms, if not all of them, say "Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe."

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

This means that instance members such as TextBox.Text {get; set;}are not reentrant.

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

Making each of those instance members thread safe could introduce a lot of overhead that most applications do not need. Instead the designers of the .Net framework decided, and I think correctly, that the burden of synchronizing access to forms controls from multiple threads should be put on the programmer.

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

[Edit]

[编辑]

Although this question only asks "why" here is a link to an article that explains "how":

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

How to: Make Thread-Safe Calls to Windows Forms Controlson MSDN

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

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

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

回答by Bj?rn Waide

Although it sounds reasonable Johns answer isn't correct. In fact even when using Invoke you're still not safe not running into dead-lock situations. When dealing with events fired on a background thread using Invoke might even lead to this problem.

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



The real reason has more to do with race conditions and lays back in ancient Win32 times. I can't explain the details here, the keywords are message pumps, WM_PAINT events and the subtle differences between "SEND" and "POST".

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



Further information can be found here hereand here.

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

回答by Quibblesome

Back in 1.0/1.1 no exception was thrown during debugging, what you got instead was an intermittent run-time hanging scenario. Nice! :) Therefore with 2.0 they made this scenario throw an exception and quite rightly so.

回到 1.0/1.1 在调试过程中没有抛出异常,你得到的是一个间歇性的运行时挂起场景。好的!:) 因此,在 2.0 中,他们使这个场景抛出异常,这是正确的。

The actual reason for this is probably (as Adam Haile states) some kind of concurrency/locky issue. Note that the normal .NET api (such as TextBox.Text = "Hello";) wraps SEND commands (that require immediate action) which can create issues if performed on separate thread from the one that actions the update. Using Invoke/BeginInvoke uses a POST instead which queues the action.

其实际原因可能是(正如 Adam Haile 所说)某种并发/锁定问题。请注意,正常的 .NET api(例如 TextBox.Text = "Hello";)包装了 SEND 命令(需要立即执行操作),如果在与执行更新操作的线程不同的线程上执行,则可能会产生问题。使用 Invoke/BeginInvoke 使用 POST 代替排队操作。

More information on SEND and POST here.

有关 SEND 和 POST 的更多信息,请点击此处

回答by Andrew Grant

There would also be the need to implement synchronization within update functions that are sensitive to being called simultaneously. Doing this for UI elements would be costly at both application and OS levels, and completely redundant for the vast majority of code.

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

Some APIs provide a way to change the current thread ownership of a system so you can temporarily (or permanently) update systems from other threads without needing to resort to inter-thread communication.

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

回答by Adam Haile

It is so that you don't have two things trying to update the control at the same time. (This could happen if the CPU switches to the other thread in the middle of a write/read) Same reason you need to use mutexes (or some other synchronization) when accessing shared variables between multiple threads.

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

Edit:

编辑:

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++)中,您可以自由尝试并执行此操作(不会像在 WinForms 中那样抛出异常),但最终将学习困难!

Ahh yes...I switch between C/C++ and C# and therefore was a little more generic then I should've been, sorry... He is correct, you cando this in C/C++, but it will come back to bite you!

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

回答by Chris Hughes

Hmm I'm not pretty sure but I think that when we have a progress controls like waiting bars, progress bars we can update their values from another thread and everything works great without any glitches.

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