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

7

我一直在尝试向我的应用程序中的一些ListBox添加一些字符串(简单的winform)- 我使用了BeginInvoke来实现

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

在我再次阅读这三行代码后,我不明白为什么在任何一个跨线程UI的例子中,无论是在谷歌还是MSDN上,我都没有看到任何对EndInvoke方法的调用?难道在这种情况下不需要调用EndInvoke吗?

2个回答

19

在 .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 控件的事件处理程序,当您运行具有太多副作用的代码时,它们将表现不良。

一个包含大量实现细节的清单。简而言之,他们没有任何共同之处,不调用EndInvoke是可以的,并且完全正常。


2
很棒的回答!Control.…InvokeTDelegate.…Invoke之间的比较列表应该出现在MSDN上。-- 我只建议你在旁注中解释一下WPF / Silverlight的“Dispatcher”,因为它在Windows Forms中不存在(这是这个问题的背景)。 - stakx - no longer contributing
谢谢这个 - 我一直在逐步检查 Control.cs,想知道如何修复空队列:threadCallBackList。现在我可以放心了,并删除导致问题的 EndInvoke! :) - Colin Zabransky

5

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

"如果需要,你可以调用 EndInvoke 来检索委托的返回值,但这并非必需。EndInvoke 将阻塞,直到可以检索到返回值。"

— 引用自 Control.BeginInvoke 的 MSDN 参考页面中的备注部分(由我强调)

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

我删除了我的帖子,因为当我编辑了几次之后,它最终看起来非常像你的 :) - Jon Skeet
据我所知,如果您不调用EndInvoke来关闭从BeginInvoke返回的IAsyncResult,则会出现内存泄漏问题。如果我有误,请纠正我。 - Yanshof
2
@Yanshof:在这个特定的情况下(Windows Forms的Control.BeginInvoke),如果不调用Control.EndInvoke,并没有任何迹象表明会发生内存泄漏。毕竟,这是一种广泛使用的做法,并且是官方文档记录的模式。如果它引起了内存泄漏,多年来使用Windows Forms的人们肯定会在某个时候注意到。 - stakx - no longer contributing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接