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

本文关键字:EndInvoke 调用 组件 线程 UI 为什么 | 更新日期: 2023-09-27 18:26:21

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

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

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

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

这在.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是很好的,也是完全正常的"。

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

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

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

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