3

您如何解释以下行为:

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”?

4

2 回答 2

3

下面的代码等效于您的示例,其中明确声明了变量,因此更容易看到发生了什么:

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 次开始
任务。IsCompleted: True
continuation1.IsCompleted: True
continuation2.IsCompleted: True
continuation1.Unwrap().IsCompleted: False
继续 1 结束

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

有一个内置的扩展方法Unwrap可以很容易地打开一个Task<Task>. Task当外部和内部任务都完成时,解包完成。展开 a 的另一种方法Task<Task>是使用await运算符两次:await await.

于 2019-11-14T12:29:02.080 回答
3

ContinueWithasync对和一无所知await。它期望Task结果,因此即使得到结果也不等待任何结果。 被await创建.ContinueWith

问题的原因是ContinueWith(async prev => 创建了一个隐式 async void delegateContinueWith没有期望Task结果的重载,因此可以为ContinueWith(async prev => 问题代码创建的唯一有效委托是:

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's 类型,你会看到它是一个System.Threading.Tasks.VoidTaskResult. ContinueWith只需将Taskasync/await 状态机生成的数据传递给下一个延续

于 2019-11-14T11:33:17.170 回答