if (foo.Invokerequired) { foo.BeginInvoke(...) } else { ... }
但使用它真的很好吗?在非GUI线程执行foo.Invokerequired之后,foo的状态可以改变.例如,如果我们在foo.Invokerequired之后关闭表单,但在foo.BeginInvoke之前,调用foo.BeginInvoke将导致InvalidOperationException:在创建窗口句柄之前,无法在控件上调用Invoke或BeginInvoke.如果我们在调用Invokerequired之前关闭表单,则不会发生这种情况,因为即使从非GUI线程调用它也会为false.
另一个例子:假设foo是一个TextBox.如果你关闭表单,之后非GUI线程执行foo.Invokerequired(这是假的,因为表单已关闭)和foo.AppendText它将导致ObjectDisposedException.
相反,在我看来,使用WindowsFormsSynchronizationContext要容易得多 – 只有当线程仍然存在时才会使用Post发布回调,而如果线程不再存在则使用Send的同步调用会抛出InvalidAsynchronousStateException.
是不是更容易使用WindowsFormsSynchronizationContext?我错过了什么吗?如果它不是真的线程安全,为什么我应该使用Invokerequired-BeginInvoke模式?你觉得哪个更好?
解决方法
所以
if (foo.Invokerequired) { foo.BeginInvoke(...) } else { ... }
可以用更安全的版本替换:
context.Post(delegate { if (foo.IsDisposed) return; ... });
假设上下文是在foo所在的同一UI线程上创建的WindowsFormsSynchronizationContext.
此版本避免了您提出的问题:
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时再次执行.
如果在自动处理的控件(如主窗体)上调用IsDisposed,则这些情况都不会意外执行代码,因为委托将立即退出,即使它是在意外时间执行的.
危险的情况是调用WindowsFormsSynchronizationContext.Send并考虑代码将被执行:它可能没有,现在有办法知道它是否做了什么.
我的结论是WindowsFormsSynchronizationContext是一个更好的解决方案,只要它被正确使用.
它可以在复杂的情况下创建sublte问题但是常见的GUI应用程序只有一个消息循环,只要应用程序本身一直很好.