异步等待阻塞主UI

3

我正在使用新的async await特性,从C#的backgroundworker进行升级。在以下代码中,我试图使用ContinueWith方法复制多个任务的执行。

        Task t1 = new Task
        (
            () =>
            {
                Thread.Sleep(10000);

                // make the Task throw an exception
                MessageBox.Show("This is T1");
            }
        );

        Task t2 = t1.ContinueWith
        (
            (predecessorTask) =>
            {

                if (predecessorTask.Exception != null)
                {
                    MessageBox.Show("Predecessor Exception within Continuation");

                    return;
                }

                Thread.Sleep(1000);
                MessageBox.Show("This is Continuation");
            },

            TaskContinuationOptions.AttachedToParent | TaskContinuationOptions.OnlyOnRanToCompletion
        );

        t1.Start();

        try
        {
           t1.Wait(); <------ Comment 
           t2.Wait(); <------ Comment 
        }
        catch (AggregateException ex)
        {
            MessageBox.Show(ex.InnerException.Message);
        }  

我的问题是当我注释掉 t1.wait 和 t2.wait 时,任务不会阻塞用户界面。然而,当我取消注释 t1.wait 和 t2.wait 时,用户界面会一直阻塞,直到线程执行完成。希望的行为是在 try/catch 块中捕获错误,而不会阻塞用户界面。我错过了什么?

1
它阻塞是因为你告诉它要这样做... - Simon Whitehead
我该如何在不阻塞用户界面的情况下,在try/catch块中等待呢? - Jim
你到底希望在这里抛出一个异常吗?我看不出会发生异常的地方。 - Simon Whitehead
1
“我正在使用新的async await特性。” “在哪里?我在你的代码中没有看到它们。” - svick
3个回答

4
如果这是在UI事件处理程序中运行的,您可以将async修改器添加到方法签名中,并将t1.Wait()更改为await t1。这将返回控制权给UI线程,当Thread.Sleep完成后,继续执行并捕获任何异常。

3

当你使用 Task.Wait() 时,基本上是在说“等待我的任务完成”。这就是为什么你会阻塞线程的原因。处理任务异常的好方法是使用 Task.ContinueWith 重载并将 OnlyOnFaulted 作为 TaskContinuationOption 传递,代码如下:

Task yourTask = new Task {...};
yourTask.ContinueWith( t=> { /*handle expected exceptions*/ }, TaskContinuationOptions.OnlyOnFaulted );

3
如果你要使用基于任务的异步模式, 那么你应该使用推荐的指南。我有一篇MSDN文章描述了其中许多内容。
特别地:
  • 使用Task.Run而不是使用带有Task.StartTask构造函数。
  • 使用await而不是ContinueWith
  • 不要使用AttachedToParent
如果你应用这些更改,你的代码将会像这样:
try
{
  await Task.Run(() =>
  {
    Thread.Sleep(10000);

    // make the Task throw an exception
    MessageBox.Show("This is T1");
  });
  await Task.Run(() =>
  {
    Thread.Sleep(1000);
    MessageBox.Show("This is Continuation");
  });
}
catch (Exception ex)
{
  MessageBox.Show(ex.Message);
}  

1
实际上,在发布这篇文章之前,我已经阅读了你的文章和博客。真的很好,但对于像我这样在await/async方面还是新手的人来说有点高级。所以,根据你的解决方案,我可以通过连续放置两个Task.Run来复制ContinueWith的行为,并忘记创建任务和Task.start。如果是这样的话,你的解决方案似乎非常优雅。 - Jim
1
Task.Run 取代了 Task 构造函数和 Task.Startawait 取代了 ContinueWith。我有一篇关于 async介绍文章,你可能会觉得有帮助。 - Stephen Cleary
一定会去查看。谢谢。 - Jim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接