繁体   English   中英

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

[英]Reasons that Control.BeginInvoke would not execute a delegate?

概观

是否有解释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所必需的,我们永远不需要返回值,因为我们的方法只是操纵UI,无论如何。

虽然非常冗长,这种格局似乎是一个比较 接受的 解决方案 然而,有一些证据表明即使这种模式在所有情况下都不能很好地发挥作用。

意见

我不排除应用程序错误并指责WinForms。 毕竟, 选择可能不会破碎 然而,我无法解释为什么代表似乎根本没有参与竞选。

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

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

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

摘要

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

还有什么可以影响委托成功传递给Control.BeginInvoke()的方式是否执行?

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

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

你需要检查IsHandleCreated - 如果那是false那么你就麻烦了因为Invoke / BeginInvoke是必要的但是因为Invoke / BeginInvoke检查哪个线程创建了Handle来做他们的魔法所以不会强有力地工作...

只有当IsHandleCreatedtrue您才会根据InvokeRequired返回的内容采取行动 - 具体内容如下:

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!" );
}

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

始终确保在第一次访问UI线程以外的线程之前已创建Handle

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

有关其他可能性,请参阅@JaredPar的答案。

BeginInvoke调用可能会失败的原因有几个。

  1. 控件及其所有父级都没有创建内部句柄。 这会在呼叫站点引起异常
  2. 控件发布了委托,但在实际在UI线程上运行之前就被销毁了
  3. UI线程停止抽取消息(通常是线程结束)

听起来#2很可能会让你感到悲伤。 我有几次开发winform应用程序这个问题。 它引起了我足够的悲痛,我从Control.BeginInvoke切换到SynchronizationContext.Current.Post SynchronizationContext.Current实例将在WinForms应用程序中的UI线程的生命周期中存活,并且IMHO比调用特定Control更可靠

您可能在第一次登录呼叫中抛出异常。 我建议看看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轻松检查任何任务是否有异常。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM