我有一个任务,当完成后,应该继续执行另一个显示winform的任务(winform之前在UI线程上初始化,所以它确实有一个句柄)。
private static Task RunningTask
{
get;
set;
}
public static UpdaterTool.Forms.UpdateResult UpdateResultForm;
private void DoWork()
{
UpdateResultForm = new Forms.UpdateResult();
//the next line forces the creation of the handle -
//otherwise InvokeRequired will later on return false.
var hackHandle = UpdateResultForm.Handle;
var ctx = TaskScheduler.FromCurrentSynchronizationContext();
RunningTask = Task.Factory.StartNew(() => DownloadAndInstallFiles(), CancelTokenSource.Token)
.ContinueWith(_ => WorkComplete(), CancelTokenSource.Token, TaskContinuationOptions.NotOnFaulted, ctx);
}
private void WorkComplete()
{
ShowResultForm();
}
private void ShowResultForm()
{
if (UpdateResultForm.InvokeRequired)
{
try
{
UpdateResultForm.Invoke(new MethodInvoker(ShowResultForm));
}
catch { }
return;
}
UpdateResultForm.Show();
}
问题在于,无论我使用的ContinueWith()的重载组合如何,UpdateResultForm都不会显示(意味着延续不会发生,工作人员会挂起"运行")或者当它是,它挂起UI,就像它期望工作线程完成或什么。我不明白为什么当我试图通过使用FromCurrentSynchronizationContext()在UI线程上显示它时会发生这种情况。
根据我的理解,在DoWork方法中,我从UI线程开始(这就是我在那里初始化表单的原因)。当代码进入Task.Factory.StartNew时,它会切换到工作线程。完成后,它继续使用WorkComplete,它只显示UI线程上先前初始化的表单。
我错过了什么?谢谢,
答案 0 :(得分:2)
使用InvokeRequired是一种强大的反模式。不知道方法运行的是什么线程并不常见。这也是这种情况,您已经使用了TaskScheduler.FromCurrentSynchronizationContext(),因此您知道该任务在UI线程上运行。再次检查是没有意义的。这也消除了尽早创建表单句柄的需要。
您遇到的问题可能是由工作线程运行DoWork()引起的。也许你也从一个任务中调用它。这使得FromCurrentSynchronizationContext()返回错误的上下文。并且将在不会引发消息循环的线程上显示该表单,它将作为一个doornail而死。或者通过阻止UI线程,等待任务完成。这将永远导致死锁,除非UI线程空闲并且可以执行ShowResultForm()方法,否则任务无法完成。
答案 1 :(得分:1)
我认为这个解决方案是:除了UI线程以外的任何线程都调用DoWork()
吗?
如果是,则考虑在UI线程上实例化UpdateResultForm
somwhere,或者如果这不可能,则在ContinueWith
操作中进行实际操作。
如果没有,则不应该出现问题。 UpdateResultForm.Handle
可能会造成一些麻烦。但是,在这种情况下不再需要此句柄,因为您已经在UI线程上,因此不必检查是否需要Invoke。
在任何一种情况下,您都可以尝试重写DoWork
方法,如下所示:
private void DoWork()
{
var ctx = TaskScheduler.FromCurrentSynchronizationContext();
RunningTask = Task.Factory
.StartNew(DownloadAndInstallFiles, CancelTokenSource.Token)
.ContinueWith(_ => ShowResultForm(), CancelTokenSource.Token, TaskContinuationOptions.NotOnFaulted, ctx);
}
private void ShowResultForm()
{
UpdateResultForm = new Forms.UpdateResult();
// Some other code
UpdateResultForm.Show();
}
甚至不再需要WorkComplete
,因为它只是通过了呼叫。希望这会对你有所帮助。
答案 2 :(得分:1)
如果您正在使用SynchronizationContext,那么您不需要InvokeRequired / Invoke。您可以遇到此处遇到的问题的唯一方法是在UI线程以外的线程上调用DoWork。
e.g。如果我拿你提供的代码,创建我自己的UpdateResult表单,添加一个CancelTokenSource成员并在构造函数中初始化它,并从UI线程运行DoWork,它工作正常。
答案 3 :(得分:0)
我解决了。这个帖子的答案都是正确的,这里的问题就像Hans建议的那样,FromCurrentSynchronizationContext()返回了错误的上下文而不是预期的UI。这是因为DoWork是从工作线程调用的,而不是从UI调用的。
在我的特定情况下,解决方案是从UI上下文调用DoWork,这意味着我必须像this blog post explains一样创建一个WindowsFormsSynchronizationContext对象。