Reputation: 151
I am using .Net 4.7.2 and C# 7
In a class there are serveral methods, all void. Some of them can be executed asynchronously, others have to be executed sequentially. So I defined a list for the parallels
private void ExecuteSequential()
{
SomeMethod1();
SomeMethod2();
ExecuteParallelAsync();
SomeMethod3();
SomeMethod4();
}
private async void ExecuteParallelAsync()
{
List<Action> methods = new List<Action>()
{
MyMethod1,
MyMethod2,
MyMethod3
};
await Task.Run( () => { Parallel.ForEach( methods , ( currentMethod ) => currentMethod.Invoke() ); } );
}
Before SomeMethod3 happens to be executed ExecuteParallelAsync should be finished entirely. Because that's not the case, apparently something is wrong with my usage of async and await.
What I am doing wrong?
Thanks in advance!
Upvotes: 0
Views: 2242
Reputation: 305
a small complete example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace test
{
class Program
{
static void Main(string[] args)
{
SomeMethod1();
ExecuteParallelAsync();
SomeMethod2();
Console.ReadKey();
}
static void SomeMethod1()
{
Console.WriteLine("SomeMethod1");
}
static void SomeMethod2()
{
Console.WriteLine("SomeMethod2");
}
static void ExecuteParallelAsync()
{
Console.WriteLine("\tstart of ExecuteParallelAsync");
var rd = new Random();
List<Action> methods = new List<Action>()
{
()=>{Thread.Sleep((int) Math.Ceiling(rd.NextDouble()*1000));Console.WriteLine("MyMethod1"); },
()=>{Thread.Sleep((int) Math.Ceiling(rd.NextDouble()*1000));Console.WriteLine("MyMethod2"); },
()=>{Thread.Sleep((int) Math.Ceiling(rd.NextDouble()*1000));Console.WriteLine("MyMethod3"); },
};
Task.WaitAll(methods.Select((action) => Task.Run(action)).ToArray());
Console.WriteLine("\tend of ExecuteParallelAsync");
}
}
}
example of a result:
SomeMethod1
start of ExecuteParallelAsync
MyMethod2
MyMethod3
MyMethod1
end of ExecuteParallelAsync
SomeMethod2
Upvotes: 0
Reputation: 34947
public void Method()
{
SomeMethod();
SomeMethod();
ExecuteParallelAsync(); //< ---fire and forget
SomeMethod();
SomeMethod();
Console.ReadLine();
}
private void SomeMethod() => Console.WriteLine($"SomeMethod");
private async void ExecuteParallelAsync()
{
Console.WriteLine($"START");
await Task.Delay(TimeSpan.FromMilliseconds(100));
Console.WriteLine($"END");
}
Output
SomeMethod
SomeMethod
START
SomeMethod
SomeMethod
END
"Oh yes, I forgot to await my function"
await ExecuteParallelAsync(); //Change 1. "I forgot to await"
Output
Compilation error (line 12, col 3): The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.
Compilation error (line 12, col 3): Cannot await 'void'
Addressing both errors from step 2.
// Change 2
// was:public void Main()
// which caused: "The 'await' operator can only (...)"
public async Task Main()
(...)
// Change 3
// was: private async void ExecuteParallelAsync()
// which caused "Compilation error (line 12, col 3): Cannot await 'void'"
private async Task ExecuteParallelAsync()
Run.
Output:
SomeMethod
SomeMethod
START
END
SomeMethod
SomeMethod
For reference, we could have waited for ExecuteParallelAsync
without the await
keyword, but await
is the correct way in all scenarios except the tiny minority where it's not.
Upvotes: 0
Reputation: 40858
Marc's answer is the correct solution, but I wanted to explain more about why.
A very key thing to understand about async
methods is that:
await
keyword is used, and await
given an incomplete Task
, the method returns - yes, even if you're only halfway through your method. The magic is in what it returns.So lets look at what your code does:
ExecuteParallelAsync()
.ExecuteParallelAsync()
starts running synchronously.Task.Run
returns a Task
object that is not complete yet.await
keyword inspects the Task
, sees that it is not complete, so it creates a new Task
, signs up the rest of the method as a continuation of that Task
(there is nothing left to do in this case, but it still happens) and returns.void
it cannot return a Task
. But it still returns!So all that means that ExecuteParallelAsync()
returns before the Task.Run
is completed, and ExecuteSequential()
has no way to tell when the work is actually complete.
This is commonly called "fire and forget" (i.e. "go do this and I don't care when or if you finish").
As Marc's answer points out, if you want to know when it's done, you need to return a Task
, which you can then await
to know when it's done.
Microsoft has very well written articles about asynchronous programming that you may benefit from reading: Asynchronous programming with async and await
Upvotes: 4
Reputation: 305
If you want an implementation without async/await sugar keyword:
private void ExecuteParallelAsync()
{
List<Action> methods = new List<Action>()
{
MyMethod1,
MyMethod2,
MyMethod3
};
Task.WaitAll(methods.Select((action) => Task.Run(action)).ToArray())
}
Upvotes: 1
Reputation: 1062520
async void
- all bets are off; in reality, you should never write an async void
method unless you're in the tiny tiny set of extreme corner cases that require it, such as async event-handlers.
Before SomeMethod3 happens to be executed ExecuteParallelAsync should be finished entirely.
how could it possibly know? it is: async void
, which means it doesn't give anything to the caller that indicates the state.
To do that, it must be:
private async Task ExecuteParallelAsync() // or ValueTask
and you must await it at the call site
await ExecuteParallelAsync();
Upvotes: 12