24

我正在测试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等待任务完成?

4

1 回答 1

47

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

对于第二个任务 ,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;
});

您正在返回一个 task of 的任务int,而不仅仅是一个Taskof 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在评论中提到的那样,另一种选择是只使用Task.Run而不是StartNew. 对于接受 a并返回 a并自动为您解包它们的Task.Run方法有一组特殊的重载:Func<Task<T>>Task<T>

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

    return 2;
});

出于这个原因,Task.Run当您创建异步 lambda 时,最好将其用作默认选项,因为它会为您“处理”此问题,尽管对于您无法使用的复杂情况,最好注意它Task.Run


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

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

如果您实际上只想等待固定的时间,那您应该这样做。

于 2012-12-28T15:32:54.340 回答