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

Yan*_*hof 7 c#

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

 myListBox.BeginInvoke(new Action(delegate()
 {
       myListBox.Items.Add( "some string" )); 
 }));
Run Code Online (Sandbox Code Playgroud)

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

Han*_*ant 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会()方法需要,它会导致如果不这样做十分钟的资源泄漏.它不是必需的控制/ Dispatcher.BeginInvoke()方法,你从来没有在实践中做到这一点.

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

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

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

  • 很棒的答案!`Control.... Invoke`和`TDelegate .... Invoke'之间的比较列表应该在MSDN上. - 我只建议你在旁注中解释WPF/Silverlight的`Dispatcher`,因为它在Windows Forms中不存在(这是这个问题的上下文). (2认同)

sta*_*ica 5

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

"你可以打电话EndInvoke从委托检索的返回值,如果neccesary,但这不是必需的.EndInvoke会会阻塞,直到返回值可以被检索到."

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

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

  • @Yanshof:在这个特定的情况下(Windows Forms的``Control.BeginInvoke`),没有调用`Control.EndInvoke`时没有内存泄漏的迹象.毕竟,这是一种普遍的做法,也是一种官方记录的模式.如果它导致内存泄漏,那么在Windows Forms存在的许多年中,某些人肯定会注意到. (2认同)