Iterating Asynchronously: How to use async & await with foreach in C#

Toby Mason-Barney
Mar 19, 2019 · 5 min read
Photo by Verne Ho from Burst

In this post, we will look at how we go about iterating using a foreach loop asynchronously. Now you may be thinking why do I need to know how to do this surely I just have to do something like this…

While this will work, it is not the best way to go about it. It will be slow as we are sitting inside the synchronous loop waiting for each task to complete one by one. Now if each task depends upon the last this is fine and need to be done in order, if not then this is wasteful.

Tasks and the promises they make

To understand why the above code is bad it is a good idea to understand Tasks and how they work.

Explaining Task in-depth is outside of the scope of this article so I have provided some links below if you would like to dive a bit deeper. I have also included a link to a comparison with JavaScript as I feel its implementation of asynchronous development is done in such a way that really helps to understand it.

I will try and provide a basic overview of what a Task is, in simple terms it is a task of work that is being done. The Task returned to you by an asynchronous method is in effect saying “Hey this is doing some work, it's not complete yet so here a Task that represents that work, once the work is complete we will come back here and continue onwards”.

In effect, it's promising that some work is going to be done and that once it's complete it will return here to continue onwards.

Looping Asynchronously

Right we are finally at the bit that this article is meant to address. There are two things to consider when we are iterating asynchronously and that is are we returning a value back or is our method void?

First, we will take a look at returning void and its different parts.

The code above is very similar to the code snippet at the top of the article, the difference is that the await keyword is used in a different way. When the method is called, the first thing we need to do is create a collection of tasks (as our method returns a Task which is the async way of saying returns void). Here we are creating a List<Task> but other collection types can be used. Once we have this we can start looping over our thingsToLoop Enumerable that has been passed into the method.

What happens next is where our listOfTasks collection comes into play, rather than calling our DoAsync method and awaiting it directly, we are calling it and adding the Task object it returns to our collection. Now if you remember from our brief section on Tasks earlier this is the promise of work to be completed.

Once we have added all of the tasks to our list we can then use a static method on the Task object called WhenAll. This method is used when you have a bunch of tasks that you want to await all at once. We then await the method and wait for all the Tasks in our collection to complete. Once done, the method returns its Task as completed to its caller and our logic is complete.

This solves our original issue in the first code snippet. We are no longer in a situation where the loop is awaiting each task one by one, we are now awaiting all tasks to finish before we return this methods Task to the caller as complete.

Now any processes that you need to be complete before your you move on with your flow should be done.

Now let's take a look at how to do the same thing but return a collection of values when our Task finishes.

As you can see the code is very similar to the example that does not return a value. We are still creating our list of Tasks and adding our returned tasks from the called method DoAsyncResult.

The difference in this example is the return types and the Task type. Rather than using Task which is the asynchronous version of void,(You can return void but it is not good practice.) we are returning Task<T> which is the Tasks generic implementation. Task<T> allows you to specify the type that will be returned once the Task has completed, in our example, this is a string.

Our DoAsyncResult returns a string, so when the Tasks we have added to our collection complete they will return a string. The WhenAll method also has a generic implementation that allows you to set the return type and as you can see we have set this to string. The return type of the WhenAll<T> method is an IEnumerable of the type specified which we can easily return.

The future is bright for this problem as a solution is on the way. C# 8 the next major release of the C# language will include Asynchronous Streams. Using this new feature you will be able to apply the await keyword directly to your foreach loops!

The code above is an example of what this will look like. I have not had a go at using this or any C# 8 feature yet but it will be adding an interesting set of features if you want to know more check them out here.

Helpful Extensions

I have included some helpful extensions methods for implementing the above. These extend the IEnumerable interface and are a quick way to run certain async methods over a collection of items you would pass in.

You may find them useful, you may not. I did for the context that I wrote them, feel free to copy and adapt them as you require.


Well, there is it, that is how you get around the problem of calling async methods in a foreach loop. This article was mainly written to show how to solve it and not dive deeply into the inner workings of the problem.

I hope you found this useful and as always I'm here to learn as much as you are so if you see anything wrong with this article or have something you feel I should add please let me know.

Thanks, Happy Coding!

Toby Mason-Barney

Written by

.Net & Node.Js developer, Vue.Js learner…well just learner…

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade