我正在测试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
不等待完成任务?
答案 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);
如果你真的只想等待一段固定的时间,那就是你应该做的事情。