2

我有一个任务,完成后应该继续执行另一个显示 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 线程上显示先前初始化的表单。

我错过了什么?谢谢,

4

4 回答 4

2

使用 InvokeRequired 是一种强大的反模式。不知道方法运行的线程并不常见。这里的情况也是如此,您已经使用了 TaskScheduler.FromCurrentSynchronizationContext(),因此您知道任务在 UI 线程上运行。再检查也没有意义。这也消除了早期创建表单句柄的需要。

您遇到的这种问题可能是由于从工作线程运行 DoWork() 引起的。也许您也从任务中调用它。这使得 FromCurrentSynchronizationContext() 返回错误的上下文。并将在不发送消息循环的线程上显示表单,它将像门钉一样死掉。或者通过阻塞 UI 线程,等待任务完成。这总是会导致死锁,除非 UI 线程空闲并且可以执行 ShowResultForm() 方法,否则任务无法完成。

于 2012-07-21T15:09:30.210 回答
1

我认为解决方案是:DoWork()在 UI 线程以外的任何线程上调用?

如果,则考虑在 UI 线程上实例化UpdateResultFormsomwhere,或者如果这不可能,则在您的ContinueWith操作中实例化 somwhere。

如果没有,那么应该没有问题。这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();
            }

EvenWorkComplete不再需要,因为它只是通过调用。希望这会帮助你。

于 2012-07-21T13:21:37.350 回答
1

如果您使用的是 SynchronizationContext,则不需要 InvokeRequired/Invoke。遇到问题的唯一方法是在 UI 线程以外的线程上调用 DoWork。

例如,如果我采用您提供的代码,创建我自己的 UpdateResult 表单,添加一个 CancelTokenSource 成员并在构造函数中对其进行初始化,然后从 UI 线程运行 DoWork,它工作正常。

于 2012-07-21T16:00:41.747 回答
0

我解决了。这个线程上的答案都是正确的,这里的问题是,就像 Hans 建议的那样,FromCurrentSynchronizationContext() 返回了错误的上下文而不是预期的 UI 上下文。这是因为 DoWork 是从工作线程调用的,而不是从 UI 调用的。

在我的特殊情况下,这里的解决方案是从 UI 上下文调用 DoWork,这意味着我必须制作一个 WindowsFormsSynchronizationContext 对象,就像这篇博文解释的那样。

于 2012-07-22T12:24:00.813 回答