是否有解释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()的行为?
答案 0 :(得分:7)
根据MSDN InvokeRequired
即使在false
应为InvokeRequired
的情况下也可以返回true
- 即在您访问InvokeRequired
的情况下在创建该控件/表单(或其父项)的Handle
之前。
基本上,您的支票不完整会导致您看到的结果。
您需要检查IsHandleCreated
- 如果是false
,那么您遇到麻烦,因为Invoke
/ BeginInvoke
是必要的但是因为{ {1}} / Invoke
检查哪个线程创建BeginInvoke
来做他们的魔法......
只有当Handle
为IsHandleCreated
时,您才会根据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
通话失败的原因有两个。
听起来#2很可能会让你感到悲伤。我有几次开发winform应用程序这个问题。这让我感到非常悲痛,我从Control.BeginInvoke
切换到SynchronizationContext.Current.Post
。 SynchronizationContext.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轻松检查任何任务是否有异常。