Whenever I see an await within a foreach (or other looping construct), I tend to get suspicious. That’s because a lot of the time, an external service (Web API, Redis, or whatever) is called repeatedly with different parameters. Imagine that DoSomethingAsync() in the above code performs an HTTP GET request, passing in x as a querystring parameter.

The above could potentially be optimised to run the requests in parallel, as described in Patterns for Asynchronous Composite Tasks in C#. But since each asynchronous call is being awaited, this has the effect of waiting for each request-response cycle to complete before starting the next one.

To illustrate the point, we can implement DoSomethingAsync() as a simple delay:

That’s 2-3 seconds, which is much better. Note though that the operations have completed in a different order from that in which they were started; this is why it’s important that they don’t depend on each other.

Do you think that’s a lot of code? No problem. We can make it more concise, with some help from LINQ.

Having said all this, it is not good to be hasty and declare war against all awaits in foreaches, because there are indeed legitimate cases for that. One example is when you have a list of commands which conform to the same interface, and they must be executed in sequence. This is perfectly fine:

When order of operations is important, running this sort of scenario in parallel can yield unexpected results.

My hope is that this will at least help to quickly identify potential performance bottlenecks due to an improper use of asynchrony. await in foreach should be eyed suspiciously as a potential code smell, but as with everything else, there is no hard and fast rule and it is perfectly fine if used correctly.

Post navigation

9 thoughts on “Avoid await in Foreach”

I would have thought making many http requests in parralel would be a risky business. Wouldn’t it overwhelm your available bandwidth? Likewise for writing many files in parralel bottlenecking IO. For these reasons I have always thought it safest in these two scenarios to do things in sequence!

It’s an interesting point that you raise. I’ve always been operating in a context where bandwidth wasn’t really an issue, especially if your requests are relatively small and your load isn’t extremely huge. When you’re trying to scale a system, performance usually becomes a problem long before bandwidth does.

Web servers are designed to handle a large number of requests in parallel, and I’m pretty sure the filesystem will likewise handle reasonable load. Having said that, in my experience I’ve seen there’s some limit beyond which parallelisation (even the async I/O kind) won’t give any further benefit, so whenever I need to process hundreds or thousands of items in parallel, I’ve found myself running batches of 50 or so in parallel at a time.

Also, if you have control over the receiving end, it’s good to provide an endpoint that can process multiple items in a single request, rather than just one item. That will save a lot of bandwidth in terms of roundtrips as well as HTTP header data per request.

Yes you can do that, as long as you understand the relationship between the parent and child tasks, and which can run in parallel and which need to run in sequence.

Also I recommend not running too many tasks in parallel at once – I’ve seen performance degradation beyond a certain threshold. It’s better to run batches and wait for one to complete before proceeding with the next.

I’m afraid you are the one who has missed the point. Yes, await in a foreach has legitimate uses (as I specified towards the end), but it is unnecessary in situations where the requests can be fired in parallel and aggregated at the end.