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
像我期待的那样阻止?
答案 0 :(得分:9)
所以这里有几个不同的错误。
首先,对于Execute
,您使用StartNew
并使用async
lambda。由于StartNew
没有Task<Task>
返回重载,如Task.Run
所做的那样,您有一个返回Task
的方法,指示异步操作何时已完成启动,而不是异步操作完成时,这意味着Task
返回的Execute
基本上会立即完成,而不是在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
方法。如前所述Execute
在您尝试提供Action
方法时接受async
。这意味着当操作运行时,代码将在您的操作完成之前继续执行。您需要使用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
会阻止,而不是异步。