Bald Bearded Builder
csharp/should-you-await-inside-your-csharp-loops

Should you await Inside Your C# Loops?

EF Core lazy loading can silently create N+1 database queries, while eager loading or targeted projections can retrieve data more efficiently.

Loads from YouTube when you press play

Transcript 3 topics

Async await in loops

0:00

If you're using asyncawait within a loop, you may not be getting the performance you're expecting from your app. It's hard to believe that asyncawait has been in c for over ten years. If you're like me, you're old enough to remember when it was released and what a big deal it was. Of course, because it's so easy to use, it's also easy to misuse, especially in loops. Let's start with a quick refresher.

Loops like for and while iterate through some code until a particular condition is met. But that means that each iteration through that loop is synchronous. Let's look at this example of using an await inside of a for each loop. The awaited task delay is actually pausing that loop while it waits for the method to execute. Looking at the output of that code, you can see the order that things are processed in exactly as we'd expect sequentially.

Now depending on your use case, that may be exactly what you want, but maybe not. For instance, if we're checking inventory levels when we're placing orders, we probably want to do that sequentially so that we don't check the inventory for two orders in parallel and accidentally assign inventory to two different orders. That would be bad. But in many cases, we'd actually improve our performance by just not pausing that loop and allowing it to continue without waiting for those operations to finish. So since putting that await inside the loop essentially makes it synchronous, let's look at how we can asynchronously loop through tasks.

Parallelizing with WhenAll

1:18

And how do we do it? Well, the short answer is Task. WinAll. Let's look at our previous code refactored to be a truly asynchronous loop. Notice we've removed the async from our for loop and instead we're just spawning up a process async task for each of those numbers.

But never fear, we aren't just leaving those behind. We're gonna actually capture those tasks and add them to a list that we'll process later. Notice like once we've finally spawned a process async task for each of those numbers, now we can wait for all of those tasks to complete. And what does the output look like? Oh snap!

Now notice they all processed but not in any particular order. That's a big gotcha! If you need your items to process sequentially, you definitely want to continue executing them inside of that loop. But if the order doesn't matter, you'll notice a big speed improvement just from running them concurrently. But that doesn't mean it's not without fault.

There are several considerations you need to take into account if you're going to use Task Win All. We've mentioned it a few times, but the first and most important is understanding that those tasks will not necessarily perform in any particular order.

Handling async exceptions

2:28

If you need sequential, you need to await inside the loop. If not, you can use Task When All. Another thing to think about is exceptions. When you're stepping through a for loop awaiting, you definitely understand the context of what's going on when that exception occurs. That's not always the case when you're running them concurrently.

But when you're running in parallel, the exception is returned to the awaiting thread which means you'll need to find some way of passing that along so that you can process any cleanup you need to do. Now I won't leave you hanging on that. One method you could use is the result pattern. Let's refactor that code again to occasionally throw an exception but to handle that with a result type object. Now after the tasks are all processed, we'll want to go through that loop of results and see if we need to do any kind of cleanup or reprocessing.

Like most things in dot net, there's multiple ways to do a thing. And now you know a couple ways for running asynchronous code in loops and when or why you would want to use each. Until next time.

Sign in to join in. Reading needs nothing.