【问题标题】:Async in Task ContinueWith behavior?任务ContinueWith行为中的异步?
【发布时间】:2019-11-14 20:38:00
【问题描述】:

您如何解释以下行为:

await Task.Run(() => { }).ContinueWith(async prev =>
{
    Console.WriteLine("Continue with 1 start");
    await Task.Delay(1000);
    Console.WriteLine("Continue with 1 end");
}).ContinueWith(prev =>
{
    Console.WriteLine("Continue with 2 start");
});

为什么我们会在“Continue with 1 end”之前得到“Continue with 2 start”?

【问题讨论】:

标签: c# asynchronous async-await


【解决方案1】:

下面的代码等同于您的示例,变量显式声明,以便更容易看到发生了什么:

Task task = Task.Run(() => { });

Task<Task> continuation1 = task.ContinueWith(async prev =>
{
    Console.WriteLine("Continue with 1 start");
    await Task.Delay(1000);
    Console.WriteLine("Continue with 1 end");
});

Task continuation2 = continuation1.ContinueWith(prev =>
{
    Console.WriteLine("Continue with 2 start");
});

await continuation2;
Console.WriteLine($"task.IsCompleted: {task.IsCompleted}");
Console.WriteLine($"continuation1.IsCompleted: {continuation1.IsCompleted}");
Console.WriteLine($"continuation2.IsCompleted: {continuation2.IsCompleted}");

Console.WriteLine($"continuation1.Unwrap().IsCompleted:" +
    $" {continuation1.Unwrap().IsCompleted}");

await await continuation1;

输出:

从 1 开始继续
继续 2 开始
task.IsCompleted: True
continuation1.IsCompleted: True
continuation2.IsCompleted: True
continuation1.Unwrap().IsCompleted: False
继续 1 结束

棘手的部分是变量continuation1,它的类型是Task&lt;Task&gt;ContinueWith 方法不会像 Task.Run 那样自动解开 Task&lt;Task&gt; 返回值,因此您最终会得到这些嵌套的任务任务。外部Task 的工作只是创建内部Task。当内部Task 已创建(未完成!)时,外部Task 已完成。这就是为什么continuation2continuation1 的内部Task 之前完成。

有一个内置的扩展方法Unwrap 可以轻松解开Task&lt;Task&gt;。当外部和内部任务都完成时,一个展开的Task 就完成了。解开Task&lt;Task&gt; 的另一种方法是使用await 运算符两次:await await

【讨论】:

    【解决方案2】:

    ContinueWithasyncawait 一无所知。它期望得到Task 结果,因此即使得到结果也不会等待任何东西。 await 是作为ContinueWith替换而创建的。

    问题的原因是ContinueWith(async prev =&gt;creates an implicit async void delegateContinueWith 没有期望 Task 结果的重载,因此可以为 ContinueWith(async prev =&gt; 问题代码创建的唯一有效委托是:

    async void (prev) 
    {
        Console.WriteLine(“Continue with 1 start”);
        await Task.Delay(1000);
        Console.WriteLine(“Continue with 1 end”);
    }
    

    async void 方法不能等待。一旦遇到await Task.Delay(),延续完成,委托让步,延续完成。如果应用程序退出,Continue with 1 end 可能永远不会被打印出来。如果应用程序在 1 秒后仍然存在,则继续执行。

    如果延迟后的代码尝试访问任何已经释放的对象,则会抛出异常。

    如果您检查prev.Result 的类型,您会发现它是System.Threading.Tasks.VoidTaskResultContinueWith 只是把 async/await 状态机生成的 Task 拿来传给下一个 continuation

    【讨论】:

      猜你喜欢
      • 2013-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-02
      相关资源
      最近更新 更多