简体   繁体   English

如何等待异步任务

[英]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();
    });
}

This seems simple enough, but naturally the output always looks like this (the order of the numbers change, of course): 这看起来很简单,但是自然地,输出总是看起来像这样(数字的顺序当然会改变):

Done waiting on tasks. 等待任务完成。

2 2

1 1个

3 3

What am I missing here? 我在这里想念什么? Why doesn't Task.WaitAll block like I'm expecting it to? 为什么Task.WaitAll不会像我期望的Task.WaitAll阻塞?

So there are several separate bugs here. 因此,这里有几个单独的错误。

First, for Execute , you're using StartNew with an async lambda. 首先,对于Execute ,您将StartNewasync lambda一起使用。 Since StartNew doesn't have a Task<Task> returning overload, like Task.Run does, you've got a method that returns a Task indicating when the asynchronous operation has finished starting , not when the asynchronous operation has finished, which means that the Task returned by Execute will be completed basically right away, rather than after Delay finishes or the action you call finishes. 由于StartNew没有像Task.Run这样的Task<Task>返回过载, Task.Run您有一个返回Task的方法,该方法指示异步操作何时完成启动 ,而不是异步操作何时完成,这意味着Execute返回的Task将立即完成,而不是在Delay完成或您调用的操作完成之后。 Additionally, there's simply no reason to use StartNew or Run at all when running asynchronous methods, you can just execute them normally and await them without pushing them to a thread pool thread. 此外,运行异步方法时根本没有理由完全使用StartNewRun ,您可以正常执行它们并await它们,而无需将它们推送到线程池线程中。

Next, Execute accepts an Action , which implies that it's a synchronous method that doesn't compute any value. 接下来, Execute接受一个Action ,这意味着它是一个同步方法,不计算任何值。 What you're providing is an asynchronous method, but as the delegate doesn't return a Task , Execute can't await it. 您提供的是一个异步方法,但是由于委托没有返回Task ,因此Execute无法await它。 If you want Execute to handle asynchronous methods, it needs to accept a delegate that returns a Task . 如果希望Execute处理异步方法,则需要接受返回Task的委托。

So given all of that Execute should look like this. 因此,鉴于所有Execute应该看起来像这样。

private static async Task Execute(Func<Task> action)
{
    await Task.Delay(TimeSpan.FromMilliseconds(5));
    await action();
}

Next onto the Main method. 接下来进入Main方法。 As mentioned before Execute is accepting an Action when you're trying to provide an async method. 如前所述,当您尝试提供async方法时, Execute接受Action This means that when the action is run the code will continued executing before your actions have finished. 这意味着在运行操作时,代码将在您的操作完成之前继续执行。 You need to adjust it to using a Task returning method. 您需要将其调整为使用Task返回方法。

After all of that , your code still has a race condition in it, at a conceptual level, that will prevent you from theoretically getting the results in the right order. 所有一切后,你的代码仍然有它的竞争条件,在概念层面,这将阻止你获得理论上的结果以正确的顺序。 You're performing 3 different operations in parallel, and as a result of that, they can finish in any order. 您正在并行执行3个不同的操作,因此,它们可以按任何顺序完成。 While you are atomically incrementing the counter, it's possible for one thread to increment the counter, then another to run, increment the counter, print its value, then have the other thread run again and print out the value, given you a possible output of what you have, even after fixing all of the bugs mentioned above. 在原子地递增计数器的同时,有可能一个线程输出一个计数器,然后运行另一个线程,递增计数器,打印其值,然后让另一个线程再次运行并打印出该值,这可能是因为您所拥有的,即使修复了上述所有错误之后也是如此。 To ensure that the values are printed in order, you need to ensure that the increment and the console write are performed atomically . 为了确保按顺序打印值,您需要确保增量和控制台写入是原子执行的

Now you can write out your Main method like so: 现在,您可以像这样写出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?

And yes, as your comment mentions, calling WaitAll will block, rather than being asynchronous. 是的,正如您的评论所提到的,调用WaitAll将阻止,而不是异步。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM