When following the async await TAP pattern particularly when executing an async method inside a loop, such as a foreach delegate.
Instead of writing a foreach loop you may want to make use of the
List(T).ForEach extension that allows you to iterate over each item and perform an operation.
But how do you call an async method inside this foreach? When coding in VS, you may add the await modifier and get a intellisense prompt saying something like ‘The ‘await’ modifier can only be used in a async lambda expression’. With a suggestion appearing to make method ‘async’. Let’s click that, and let VS do it’s thing, that sounds good! (But it isn’t).
This is an example of the code I was writing last week. orderAuthorisations.ForEach(async oa => await _orderAuthorisationService.UploadChallengeAsync(oa));
Foreach does not support async await so it felt wrong to add the async modifier as I could see this would return async void. After some research I found out that the foreach above will run all of the async operations synchronously despite the async await modifiers. The async lambda above will be compiled into an async void delegate as opposed to returning a Task in the method signature. Returning void when using the async modifier should only really be used in event handlers often used in WinForms & WPF which often exist at the highest level. Async methods should always return a Task. The reason for this is when an exception is raised inside this async method it will not bubble up to the outer try catch if it’s return type is void. (The exception is placed on the Task object normally, and when it is void there is no task object). This can prove difficult when error handling and debugging, and is considered an anti-pattern.
Async void methods are normally used in a ‘Fire & Forget’ situation when you do not care about the end result, if they fail or if they succeed, because they do not report back the end result.
A best practice approach is to use
Linq to build up a collection of tasks for each item and create a task and await it, so that it completes when all of the tasks in the collection have completed. This way you know the operation has ‘Finished’ you will also be able to wrap this in a try, catch block because each items returns a task and the exception will bubble up.
// Get all the of the tasks to be executed. var tasks = orderAuthorisations.Select(oa => _orderAuthorisationService.UploadChallengeAsync(oa)); // Create a task that will complete when all of the Task // In the enumerable collection have completed & await it. await Task.WhenAll(tasks);
It’s also worth bearing in mind that
Parallel.ForEach is not asynchronous, it synchronously processes tasks in parallel. Asynchronous & Parallel both function differently & should not be mixed. (Parallel does not support async await).