[英]How to wait on Async tasks
static void Main(string[] args)
{
Action myAction = async () =>
{
await Task.Delay(5);
Console.WriteLine(Interlocked.Add(ref ExecutionCounter, 1));
};
var actions = new[] { myAction, myAction, myAction };
Task.WaitAll(actions.Select(a => Execute(a)).ToArray()); //This blocks, right?
Console.WriteLine("Done waiting on tasks.");
Console.ReadLine();
}
static int ExecutionCounter = 0;
private static Task Execute(Action a)
{
return Task.Factory.StartNew(async () =>
{
await Task.Delay(5);
a();
});
}
这看起来很简单,但是自然地,输出总是看起来像这样(数字的顺序当然会改变):
等待任务完成。
2
1个
3
我在这里想念什么? 为什么Task.WaitAll
不会像我期望的Task.WaitAll
阻塞?
因此,这里有几个单独的错误。
首先,对于Execute
,您将StartNew
与async
lambda一起使用。 由于StartNew
没有像Task.Run
这样的Task<Task>
返回过载, Task.Run
您有一个返回Task
的方法,该方法指示异步操作何时完成启动 ,而不是异步操作何时完成,这意味着Execute
返回的Task
将立即完成,而不是在Delay
完成或您调用的操作完成之后。 此外,运行异步方法时根本没有理由完全使用StartNew
或Run
,您可以正常执行它们并await
它们,而无需将它们推送到线程池线程中。
接下来, Execute
接受一个Action
,这意味着它是一个同步方法,不计算任何值。 您提供的是一个异步方法,但是由于委托没有返回Task
,因此Execute
无法await
它。 如果希望Execute
处理异步方法,则需要接受返回Task
的委托。
因此,鉴于所有Execute
应该看起来像这样。
private static async Task Execute(Func<Task> action)
{
await Task.Delay(TimeSpan.FromMilliseconds(5));
await action();
}
接下来进入Main
方法。 如前所述,当您尝试提供async
方法时, Execute
接受Action
。 这意味着在运行操作时,代码将在您的操作完成之前继续执行。 您需要将其调整为使用Task
返回方法。
所有这一切后,你的代码仍然有它的竞争条件,在概念层面,这将阻止你获得理论上的结果以正确的顺序。 您正在并行执行3个不同的操作,因此,它们可以按任何顺序完成。 在原子地递增计数器的同时,有可能一个线程输出一个计数器,然后运行另一个线程,递增计数器,打印其值,然后让另一个线程再次运行并打印出该值,这可能是因为您所拥有的,即使修复了上述所有错误之后也是如此。 为了确保按顺序打印值,您需要确保增量和控制台写入是原子执行的 。
现在,您可以像这样写出Main
方法:
int ExecutionCounter = 0;
object key = new object();
Func<Task> myAction = async () =>
{
await Task.Delay(TimeSpan.FromMilliseconds(5));
lock (key)
{
Console.WriteLine(++ExecutionCounter);
}
};
var actions = new[] { myAction, myAction, myAction };
Task.WaitAll(actions.Select(a => Execute(a)).ToArray()); //This blocks, right?
是的,正如您的评论所提到的,调用WaitAll
将阻止,而不是异步。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.