问题描述
任何时候初学者都会问:如何更新来自 C# 中另一个线程的 GUI?,答案很直接:
Anytime the beginner asks something like: How to update the GUI from another thread in C#?, the answer is pretty straight:
if (foo.InvokeRequired)
{
foo.BeginInvoke(...)
} else {
...
}
但是真的好用吗?在非 GUI 线程执行 foo.InvokeRequired
之后,foo
的状态可以改变.例如,如果我们在 foo.InvokeRequired
之后但在 foo.BeginInvoke
之前关闭表单,则调用 foo.BeginInvoke
将导致 InvalidOperationException
:在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke. 如果我们在调用 InvokeRequired
之前关闭窗体,则不会发生这种情况, 因为即使从非 GUI 线程调用它也会是 false
.
But is it really good to use it? Right after non-GUI thread executes foo.InvokeRequired
the state of foo
can change. For example, if we close form right after foo.InvokeRequired
, but before foo.BeginInvoke
, calling foo.BeginInvoke
will lead to InvalidOperationException
: Invoke or BeginInvoke cannot be called on a control until the window handle has been created. This wouldn't happen if we close the form before calling InvokeRequired
, because it would be false
even when called from non-GUI thread.
另一个例子:假设 foo
是一个 TextBox
.如果您关闭表单,并且在该非 GUI 线程执行 foo.InvokeRequired
(这是错误的,因为表单已关闭)和 foo.AppendText
之后,它将导致 ObjectDisposedException
.
Another example: Let's say foo
is a TextBox
. If you close form, and after that non-GUI thread executes foo.InvokeRequired
(which is false, because form is closed) and foo.AppendText
it will lead to ObjectDisposedException
.
相比之下,在我看来,使用 WindowsFormsSynchronizationContext
更容易 - 只有当线程仍然存在时才会使用 Post
发布回调,并且使用 Send 进行同步调用
如果线程不再存在,则抛出 InvalidAsynchronousStateException
.
In contrast, in my opinion using WindowsFormsSynchronizationContext
is much easier - posting callback by using Post
will occur only if thread still exists, and synchronous calls using Send
throws InvalidAsynchronousStateException
if thread not exists anymore.
使用 WindowsFormsSynchronizationContext
不是更简单吗?我错过了什么吗?如果 InvokeRequired-BeginInvoke 模式不是真正的线程安全,为什么要使用它?你觉得哪个更好?
Isn't using WindowsFormsSynchronizationContext
just easier? Am I missing something? Why should I use InvokeRequired-BeginInvoke pattern if it's not really thread safe? What do you think is better?
推荐答案
WindowsFormsSynchronizationContext
通过将自身附加到绑定到创建上下文的线程的特殊控件来工作.
WindowsFormsSynchronizationContext
works by attaching itself to a special control that is bound to the thread where the context is created.
所以
if (foo.InvokeRequired)
{
foo.BeginInvoke(...)
} else {
...
}
可以换成更安全的版本:
Can be replaced with a safer version :
context.Post(delegate
{
if (foo.IsDisposed) return;
...
});
假设 context
是在 foo
所在的同一 UI 线程上创建的 WindowsFormsSynchronizationContext
.
Assuming that context
is a WindowsFormsSynchronizationContext
created on the same UI thread that foo
was.
这个版本避免了你引起的问题:
This version avoid the problem you evoke :
在非 GUI 线程执行 foo.InvokeRequired 之后,foo 的状态可以改变.例如,如果我们在 foo.InvokeRequired 之后但在 foo.BeginInvoke 之前关闭窗体,则调用 foo.BeginInvoke 将导致 InvalidOperationException:在创建窗口句柄之前无法在控件上调用 Invoke 或 BeginInvoke.如果我们在调用 InvokeRequired 之前关闭表单就不会发生这种情况,因为即使从非 GUI 线程调用它也会为假.
Right after non-GUI thread executes foo.InvokeRequired the state of foo can change. For example, if we close form right after foo.InvokeRequired, but before foo.BeginInvoke, calling foo.BeginInvoke will lead to InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created. This wouldn't happen if we close the form before calling InvokeRequired, because it would be false even when called from non-GUI thread.
<小时>
如果您使用多个消息循环或多个 UI 线程,请注意 WindowsFormsSynchronizationContext.Post
的一些特殊情况:
WindowsFormsSynchronizationContext.Post
仅当创建委托的线程上仍有消息泵时才会执行委托.如果没有什么都没有发生并且没有引发异常.
另外,如果稍后将另一个消息泵附加到线程(通过第二次调用Application.Run
例如)委托将执行(这是因为系统为每个线程维护一个消息队列,但不知道有人是否从其中抽取消息)WindowsFormsSynchronizationContext.Send
如果绑定的线程不再活动,则抛出InvalidAsynchronousStateException
.但是如果它绑定的线程是存活的并且没有运行消息循环它不会立即执行但是仍然会被放置在消息队列中并且如果Application.Run
再次执行.
WindowsFormsSynchronizationContext.Post
will execute the delegate only if there still is a message pump on the thread where it was created. If there isn't nothing happens and no exception is raised.
Also if another message pump is later attached to the thread (Via a second call toApplication.Run
for example) the delegate will execute (It's due to the fact that the system maintain a message queue per thread without any knowledge about the fact that someone is pumping message from it or not)WindowsFormsSynchronizationContext.Send
will throwInvalidAsynchronousStateException
if the thread it's bound to isn't alive anymore. But if the thread it's bound to is alive and doesn't run a message loop it won't be executed immediately but will still be placed on the message queue and executed ifApplication.Run
is executed again.
如果在自动释放的控件(如主窗体)上调用 IsDisposed
,则这些情况都不应意外执行代码,因为委托将立即退出,即使它是在意外时间执行的.
None of these cases should execute code unexpectedly if IsDisposed
is called on a control that is automatically disposed (Like the main form) as the delegate will immediately exit even if it's executed at an unexpected time.
危险的情况是调用 WindowsFormsSynchronizationContext.Send
并考虑到代码将被执行:它可能不会,现在有办法知道它是否做了任何事情.
The dangerous case is calling WindowsFormsSynchronizationContext.Send
and considering that the code will be executed: It might not, and there is now way to know if it did anything.
我的结论是,只要正确使用 WindowsFormsSynchronizationContext
是更好的解决方案.
My conclusion would be that WindowsFormsSynchronizationContext
is a better solution as long as it's correctly used.
它可以在复杂的情况下产生微妙的问题,但常见的 GUI 应用程序只有一个消息循环,只要应用程序本身一直正常运行,它就会一直存在.
It can create sublte problems in complex cases but common GUI applications with one message loop that live as long as the application itself will always be fine.
这篇关于为什么 InvokeRequired 优于 WindowsFormsSynchronizationContext?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!