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

Mur*_*nze 23 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;
Run Code Online (Sandbox Code Playgroud)

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

Ser*_*rvy 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;
});
Run Code Online (Sandbox Code Playgroud)

你返回的任务的任务int,不只是一个Taskint.内部任务完成后,外部任务即"完成".当你使用时,WhenAll你不关心外部任务何时完成,你关心内部任务何时完成.有很多方法可以解决这个问题.一个是使用Unwrap.

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

    return 2;
}).Unwrap();
Run Code Online (Sandbox Code Playgroud)

现在你有了预期的,Task<int>并且WhenAll至少需要2000毫秒,正如预期的那样.您还可以添加另一个await调用来执行相同的操作:

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

    return 2;
});
Run Code Online (Sandbox Code Playgroud)

正如svick 在评论中所提到的另一个选择就是使用Task.Run而不是StartNew. Task.Run有一组特殊的重载方法,用于获取Func<Task<T>>和返回a Task<T>并自动为你打开它们的方法:

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

    return 2;
});
Run Code Online (Sandbox Code Playgroud)

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


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

var t3 = Task.Delay(3000);
await Task.WhenAll(t1, t2, t3);
Run Code Online (Sandbox Code Playgroud)

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

  • 还有另一种选择:使用`Task.Run()`.它自己解开任务,所以它可能比使用`StartNew()`与`Unwrap()`结合使用更好. (4认同)