Control.BeginInvoke不会执行委托的原因?

时间:2012-01-23 21:07:54

标签: c# winforms multithreading begininvoke

概述

是否有解释Control.BeginInvoke()不执行传递的委托?

代码示例

我们在Winforms应用程序中采用了以下模式,以便在UI线程上安全地执行UI相关工作:

private Control hiddenControl = new Control();

private void uiMethod()
{
  MethodInvoker uiDelegate = new MethodInvoker(delegate()
  {
    Logging.writeLine("Start of uiDelegate");
    //ui releated operations
    childDialog = new ChildDialog();
    childDialow.show();
    Logging.writeLine("End of uiDelegate");
  });

  if (hiddenControl.InvokeRequired)
  {
    Logging.writeLine("Start of InvokeRequired block");
    hiddenControl.BeginInvoke(uiDelegate);
    Logging.writeLine("End of InvokeRequired block");
  }
  else
  {
    uiDelegate();
  }
}

在这里,我们显式创建一个控件“hiddenControl”,以便在UI线程上运行委托。我们从不调用endInvoke,因为对于Control.BeginInvoke来说它显然是not required,我们永远不需要返回一个值,因为我们的方法只是操纵UI。

虽然非常详细,但这种模式似乎是relatively well accepted solution。但是,有一些evidence即使这种模式在所有情况下都不能很好地发挥作用。

观察

我不排除应用程序错误并指责WinForms。毕竟,select probably isn't broken。然而,我无法解释为什么代表似乎根本没有参与竞选。

在我们的例子中,我们有时会观察到“开始uiDelegate”日志消息从未在某些线程场景中执行,即使“Start of InvokeReqiured block”和“End of InvokeRequired block”成功执行。

复制此行为非常困难,因为我们的应用程序是作为DLL提供的;我们的客户在自己的应用程序中运行它。因此,我们无法保证可以调用这些方法的方式或方式。

我们排除了UI线程饥饿,因为观察到UI没有锁定。据推测,如果正在更新UI,则消息泵可以运行并可用于从消息队列中提取消息并执行其代理。

摘要

鉴于此信息,我们可以尝试使这些调用更具防弹性吗?如前所述,我们对给定应用程序中的其他线程的控制相对较少,并且不控制调用这些方法的上下文。

还有哪些会影响委托成功传递给Control.BeginInvoke()的行为?

3 个答案:

答案 0 :(得分:7)

根据MSDN InvokeRequired即使在false应为InvokeRequired的情况下也可以返回true - 即在您访问InvokeRequired的情况下在创建该控件/表单(或其父项)的Handle之前。

基本上,您的支票不完整会导致您看到的结果。

您需要检查IsHandleCreated - 如果是false,那么您遇到麻烦,因为Invoke / BeginInvoke是必要的但是因为{ {1}} / Invoke检查哪个线程创建BeginInvoke来做他们的魔法......

只有当HandleIsHandleCreated时,您才会根据true返回的内容采取行动 - 以下内容:

InvokeRequired

因此,以下内容对于避免此问题很重要

始终确保在第一次访问UI线程以外的线程之前已创建if (control.IsHandleCreated) { if (control.InvokeRequired) { control.BeginInvoke(action); } else { action.Invoke(); } } else { // in this case InvokeRequired might lie - you need to make sure that this never happens! throw new Exception ( "Somehow Handle has not yet been created on the UI thread!" ); }

根据MSDN,您只需要在UI线程中引用Handle以强制创建它 - 在您的代码中,这必须在您第一次从任何线程访问该控件/表单之前发生那不是UI线程。

对于其他可能性,请参阅@JaredPar的答案。

答案 1 :(得分:3)

BeginInvoke通话失败的原因有两个。

  1. 控件及其所有父级没有创建内部句柄。这会在呼叫站点引起异常
  2. 控件发布了委托,但在实际在UI线程上运行之前就被销毁了
  3. UI线程停止传送消息(通常是线程结束)
  4. 听起来#2很可能会让你感到悲伤。我有几次开发winform应用程序这个问题。这让我感到非常悲痛,我从Control.BeginInvoke切换到SynchronizationContext.Current.PostSynchronizationContext.Current实例将在WinForms应用程序中的UI线程的生命周期中存活,并且IMHO比调用特定的Control

    更可靠

答案 2 :(得分:3)

您可能在第一次登录呼叫中抛出异常。我建议看看TPL(如果你使用的是.Net 4.0)。任务可以使您的代码更具可读性,您可以执行以下操作

Task t = Task.Factory.StartNew(()=>{...Do Some stuff not on the UI thread...});
Task continuationTask = t.ContinueWith((previousTask)=>{...Do your UI stuff now 
         (let the Task Scheduler deal with jumping back on the UI thread...},
    TaskScheduler.FromCurrentSynchronizationContext());

然后,您还可以通过continuationTask.Exceptions轻松检查任何任务是否有异常。