简体   繁体   中英

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

1

3

What am I missing here? Why doesn't Task.WaitAll block like I'm expecting it to?

So there are several separate bugs here.

First, for Execute , you're using StartNew with an async 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. 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.

Next, Execute accepts an Action , which implies that it's a synchronous method that doesn't compute any value. What you're providing is an asynchronous method, but as the delegate doesn't return a Task , Execute can't await it. If you want Execute to handle asynchronous methods, it needs to accept a delegate that returns a Task .

So given all of that Execute should look like this.

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

Next onto the Main method. As mentioned before Execute is accepting an Action when you're trying to provide an async method. 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.

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. 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:

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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