如何等待异步任务

时间:2017-05-31 13:42:53

标签: c# task

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像我期待的那样阻止?

1 个答案:

答案 0 :(得分:9)

所以这里有几个不同的错误。

首先,对于Execute,您使用StartNew并使用async lambda。由于StartNew没有Task<Task>返回重载,如Task.Run所做的那样,您有一个返回Task的方法,指示异步操作何时已完成启动,而不是异步操作完成时,这意味着Task返回的Execute基本上会立即完成,而不是在Delay之后完成完成或你调用的动作完成。此外,在运行异步方法时根本没有理由使用StartNewRun,您可以正常执行它们并await它们而不将它们推送到线程池线程。

接下来,Execute接受Action,这意味着它是一种不会计算任何值的同步方法。您提供的是异步方法,但由于代理人未返回TaskExecute无法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会阻止,而不是异步。