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.