好的,那么第二个任务比较简单,让我们处理它。
对于第二个任务,t2
,您不会对 Task.Delay(1000)
的结果执行任何操作。您不会使用 await
,也不会使用 Wait
等待。鉴于该方法不是 async
,我认为您想要进行阻塞等待。为此,您需要在 Delay
调用的末尾添加 Wait()
使其成为阻塞等待,或者只是使用 Thread.Sleep()
。
对于第一个任务,您正在受到使用 var
的事实的影响。当您不使用 var
时,发生的情况更清晰:
Task<Task<int>> t1 = Task.Factory.StartNew(async () =>
{
await Task.Delay(2000);
return 2;
});
你返回的是一个任务嵌套的int
,而不只是一个Task
且外部任务会在内部任务启动后立即"完成"。当你使用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在评论中提到的,另一个选择是仅使用Task.Run
而不是StartNew
。Task.Run
有一组特殊的重载方法,用于接受一个Func<Task<T>>
并返回一个Task<T>
对象,并自动为您取消包装:
Task<int> t1 = Task.Run(async () =>
{
await Task.Delay(2000);
return 2;
});
因此,当您创建异步 Lambda 时,最好使用 Task.Run
作为默认选项,因为它会为您“处理”此问题,但在无法使用 Task.Run
的复杂情况下,最好意识到这一点。
最后我们来讨论你没有采取的选项,这实际上是你应该在这种情况下采取的选项。由于 Task.Delay
已经返回了一个任务,因此没有必要首先将其放入 StartNew
中。您可以不必将其包装起来,而是直接使用它:
var t3 = Task.Delay(3000);
await Task.WhenAll(t1, t2, t3);
如果你只是想等待一段固定的时间,那么你应该这样做。