为什么Task.Delay在这种情况下不起作用

时间:2012-12-28 15:14:36

标签: c# async-await

我正在测试async,我发现这种情况我无法理解:

var watch = Stopwatch.StartNew();

var t1 = Task.Factory.StartNew(async () =>
{
    await Task.Delay(2000);

    return 2;
});

var t2 = Task.Factory.StartNew(() =>
{
    Task.Delay(1000);

    return 1; 
});

await Task.WhenAll(t1, t2);

var result = watch.ElapsedMilliseconds;

我想了解为什么结果始终为0!为什么不是1000,2000或两个任务3000的总和?为什么Task.WhenAll不等待完成任务?

1 个答案:

答案 0 :(得分:44)

好吧,第二个是简单的,所以让我们处理那个。

对于第二项任务t2,您不会对Task.Delay(1000)的结果执行任何操作。你没有await它,你没有Wait它等等。鉴于该方法不是async我认为你的意思是阻塞等待。要做到这一点,您需要在Wait()调用结束时添加Delay以使其成为阻止等待,或者只使用Thread.Sleep()


对于第一项任务,您被var使用时被咬了。当你不使用var

时,更清楚的是发生了什么
Task<Task<int>> t1 = Task.Factory.StartNew(async () =>
{
    await Task.Delay(2000);

    return 2;
});

您正在返回int任务的任务,而不只是Task int。内部任务完成后,外部任务即“完成”。当您使用WhenAll时,您不关心外部任务何时完成,您关心内部任务何时完成。有很多方法可以解决这个问题。一种是使用Unwrap

Task<int> t1 = Task.Factory.StartNew(async () =>
{
    await Task.Delay(2000);

    return 2;
}).Unwrap();

现在您的预期Task<int>WhenAll将至少需要2000毫秒。您还可以添加另一个await调用来执行相同的操作:

Task<int> t1 = await Task.Factory.StartNew(async () =>
{
    await Task.Delay(2000);

    return 2;
});

正如svick in a comment所提到的,另一个选择就是使用Task.Run代替StartNew。对于采用Task.Run并返回Func<Task<T>>并为您自动解包的方法,Task<T>有一组特殊的重载:

Task<int> t1 = Task.Run(async () =>
{
    await Task.Delay(2000);

    return 2;
});

因此,当您创建异步lambdas时,最好使用Task.Run作为默认选项,因为它将为您“处理”此问题,尽管最好在复杂情况下了解它你不能使用Task.Run


最后,我们来到了你没有做的选项,这是你在这种情况下应该实际做的事情。由于Task.Delay已经返回了一个任务,因此首先不需要将其放在StartNew中。而不是创建嵌套任务并使用Unwrap,你可以不把它包装在第一位:

var t3 = Task.Delay(3000);
await Task.WhenAll(t1, t2, t3);

如果你真的只想等待一段固定的时间,那就是你应该做的事情。