为什么在跨线程UI组件调用中没有EndInvoke?

我一直在尝试将一些字符串添加到我的应用程序(简单的winform)上的一些ListBox中 – 我在使用BeginInboke时这样做了

myListBox.BeginInvoke(new Action(delegate() { myListBox.Items.Add( "some string" )); })); 

在我再次阅读这3行之后 – 我不明白为什么在我在谷歌和MSDN上看到的任何跨线程UI的例子中我都没有看到任何EndInvoke的调用? 有没有理由不在这种情况下调用EndInvoke?

这是.NET中一个不幸的命名选择。 Control.BeginInvoke和Dispatcher.BeginInvoke方法与委托的方法具有相同的名称,但操作完全不同。 主要区别:

  • 委托的BeginInvoke()方法始终是类型安全的,它具有与委托声明完全相同的参数。 Control / Dispatcher版本完全缺少这一点,参数通过类型为object []的params数组传递。 当你得到一个错误的参数时,编译器不会告诉你,它会在运行时发生爆炸

  • 委托的Invoke()方法在同一个线程上运行委托目标。 与Control / Dispatcher.Invoke()不同,它们封送对UI线程的调用

  • 捕获委托的BeginInvoke()目标中引发的exception,不会导致程序失败。 在调用EndInvoke()时重新抛出。 对于Control / Dispatcher.BeginInvoke()来说,情况并非如此,它们会在UI线程上引发exception。 没有合适的方法来捕获exception,Application.UnhandledException存在的一个更大的原因。

  • 需要调用委托的EndInvoke()方法,如果不这样做,则会导致10分钟的资源泄漏。 Control / Dispatcher.BeginInvoke()方法不需要它,在实践中你永远不会这样做。

  • 使用Control / Dispatcher.Invoke()是有风险的,它很容易导致死锁。 当UI线程未准备好调用目标时触发,并执行某些不明智的操作,如等待线程完成。 对于委托来说不是问题,至少因为它的Invoke()方法不使用线程。

  • 在UI线程上调用Control / Dispatcher.BeginInvoke()是受支持的方案。 正如预期的那样,目标仍然在UI线程上运行。 但是后来,在UI线程再次空闲并重新进入调度程序循环之后。 这实际上是一个非常有用的function,它有助于解决棘手的重入问题。 特别是在用于UI控件的事件处理程序中,当您运行具有太多副作用的代码时,这些控件将会出错。

一个包含大量实施细节的大清单。 TLDR版本当然是:“它们没有任何共同之处,没有调用EndInvoke很好而且完全正常”。

Control.BeginInvoke似乎并不完全遵循通常的BeginX / EndX模式,即异步编程模型(APM) 。 通常,您必须为每个BeginX调用EndX ,但在Control.BeginInvoke的情况下,这不是严格要求的:

“如果需要, 可以调用EndInvoke从委托中检索返回值,但这不是必需的 .EndInvoke将阻塞,直到可以检索返回值。”

– 来自Control.BeginInvoke的MSDN参考页面上的备注部分(由我强调)

在实践中,几乎没有必要。 这是因为通常调用该方法让一些代码在更新UI的UI线程上执行。 更新UI通常不会产生任何返回值,因此您不希望调用EndInvoke