7

我一直在尝试向我的应用程序(简单的 winform)上的一些 ListBox 添加一些字符串 - 我使用 BeginInboke 做到了

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

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

4

2 回答 2

17

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

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

  • 委托的 Invoke() 方法在同一线程上运行委托目标。Control/Dispatcher.Invoke() 的情况并非如此,它们将调用编组到 UI 线程

  • 在委托的 BeginInvoke() 目标中引发的异常被捕获并且不会导致程序失败。在调用 EndInvoke() 时重新抛出。Control/Dispatcher.BeginInvoke() 根本不是这种情况,它们会在 UI 线程上引发异常。没有像样的方法来捕获异常,这是 Application.UnhandledException 存在的更大原因之一。

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

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

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

一个包含大量实现细节的大列表。TLDR 版本当然是:“他们没有任何共同点,不调用 EndInvoke 很好,完全正常”。

于 2013-08-02T14:31:12.873 回答
5

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

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

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

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

于 2013-08-02T12:07:31.907 回答