为什么InvokeRequired比WindowsFormsSynchronizationContext更受欢迎?

pro*_*ick 7 .net c# multithreading synchronization winforms

任何时候初学者都会问:如何从C#中的另一个线程更新GUI?,答案很简单:

if (foo.InvokeRequired)
{
    foo.BeginInvoke(...)
} else {
    ...
}
Run Code Online (Sandbox Code Playgroud)

但使用它真的很好吗?在非GUI线程执行后foo.InvokeRequired,状态foo可以改变.例如,如果我们在之后foo.InvokeRequired,但之前关闭表单foo.BeginInvoke,则调用foo.BeginInvoke将导致InvalidOperationException:在创建窗口句柄之前,无法在控件上调用Invoke或BeginInvoke.如果我们在调用之前关闭表单,这不会发生InvokeRequired,因为false即使从非GUI线程调用它也是如此.

另一个例子:让我们说foo是一个TextBox.如果你关闭表单,然后执行非GUI线程foo.InvokeRequired(这是错误的,因为表单已关闭),foo.AppendText它将导致ObjectDisposedException.

相比之下,在我看来,使用WindowsFormsSynchronizationContext更容易 - Post只有当线程仍然存在时才会发布使用回调,如果线程不再存在则使用Sendthrows进行同步调用InvalidAsynchronousStateException.

是不是WindowsFormsSynchronizationContext更容易使用?我错过了什么吗?如果它不是真的线程安全,为什么我应该使用InvokeRequired-BeginInvoke模式?你觉得哪个更好?

Jul*_*lia 7

WindowsFormsSynchronizationContext 通过将自身附加到绑定到创建上下文的线程的特殊控件来工作.

所以

if (foo.InvokeRequired)
{
    foo.BeginInvoke(...)
} else {
    ...
}
Run Code Online (Sandbox Code Playgroud)

可以用更安全的版本替换:

context.Post(delegate
{
    if (foo.IsDisposed) return;
    ...
});
Run Code Online (Sandbox Code Playgroud)

假设它contextWindowsFormsSynchronizationContext在同一个UI线程上创建的foo.

此版本避免了您提出的问题:

在非GUI线程执行foo.InvokeRequired之后,foo的状态可以改变.例如,如果我们在foo.InvokeRequired之后关闭表单,但在foo.BeginInvoke之前,调用foo.BeginInvoke将导致InvalidOperationException:在创建窗口句柄之前,无法在控件上调用Invoke或BeginInvoke.如果我们在调用InvokeRequired之前关闭表单,则不会发生这种情况,因为即使从非GUI线程调用它也是错误的.


WindowsFormsSynchronizationContext.Post如果您使用多个消息循环或多个UI线程,请注意一些特殊情况:

  • WindowsFormsSynchronizationContext.Post只有在创建它的线程上仍有消息泵时,才会执行该委托.如果没有任何反应,则不会引发异常.
    此外,如果稍后将另一个消息泵附加到线程(Application.Run例如通过第二次调用),则委托将执行(这是因为系统维护每个线程的消息队列,而不知道某人正在发送消息的事实来自或不)
  • WindowsFormsSynchronizationContext.SendInvalidAsynchronousStateException如果它所绑定的线程不再存在则会抛出.但是如果它绑定的线程是活动的并且没有运行消息循环,它将不会立即执行但仍将被放置在消息队列中并且如果Application.Run再次执行则执行.

如果IsDisposed在自动处理的控件(如主窗体)上调用,则这些情况都不会意外执行代码,因为委托将立即退出,即使它是在意外时间执行的.

危险的情况是调用WindowsFormsSynchronizationContext.Send并考虑代码将被执行:它可能不会,现在有办法知道它是否做了什么.


我的结论是,WindowsFormsSynchronizationContext只要它被正确使用,这是一个更好的解决方案.

它可以在复杂的情况下创建sublte问题,但常见的GUI应用程序只有一个消息循环,只要应用程序本身一直很好.