TheSharperDev

Educating about C# and F#

C#’s WhenAll and Exception Handling

 Photo by Jerry Zhang on Unsplash

C#’s WhenAll method helps save time when processing lists of tasks. When thinking about exceptions, I couldn’t find good patterns that allowed me to access the full list of tasks after they’ve completed. This post outlines the solution I came up with, which hopefully will help you out too.

The Benefits of WhenAll

Lets backup and give a brief introduction to why WhenAll is so useful. Say you have a list of tasks, how might you go about running them all? A naive approach would be to use a for loop and await them all.

Here’s is a simple interface that defines a method, then an implementation that takes 3 seconds to complete, simulating some sort of I/O request.

If I had 4 of these such tasks, I could get the results using a for loop.

What’s wrong with this approach?

Since I’m processing these pings one at a time inside of our for loop, this method takes ~12 seconds to complete. (3 + 3 + 3 + 3 = 12).

A much better approach would be to use WhenAll to start them all at the same time, then process them whenever they all are finished.

Notice how I’m calling Ping outside of my for loop, which starts all these tasks at the same time. Then the call to WhenAll will wait until they’re all finished, then process each in my for loop.

Since all tasks start at the same time, this method only takes ~3 seconds to complete. Significantly speeding up this code.

WhenAll Exceptions

If all you take away from this article is to not await things inside a for each loop, that’s great. But another problem now exists, what happens if one of our tasks throws an exception?

Here’s a new ping type that simulates some failure.

Lets also change our pingTasks to include two of these BrokenPings.

Now what happens to my code? The WhenAll call still waits for everything to complete, but now there’s an exception to deal with. Since there’s no error mechanism to catch an exception the exception goes up the call stack.

What happens if you introduce a try catch around the WhenAll call?

That only catches the first exception. But what about all of our tasks? We had a total of four tasks, 2 working and 2 broken.

As far as I can tell, WhenAll only throws the first exception. If you had 100 tasks and 1 of them threw an exception, you don’t have access to the 99 tasks that completed successfully.

Which is a bit annoying, but I found a fairly simple work around.

Buffering Your Method Call

Lets introduce a new method whose sole responsibility is to catch exceptions for individual tasks.

Then using this BufferCall method in our original method.

This BufferCall method makes the Ping call and catches any exceptions for an individual ping.

This helps you in two ways:

  1. Gives you the ability to specify what the default should be in case of failure. In this case we returned false.
  2. It also gives you access to the tasks that completed successfully. As far as I can tell, in a normal WhenAll you just lose access to everything else when an exception occurs.

Depending on your situation and what you’re computing, those are both really useful.

Final Thoughts

Writing non-blocking code is important! C# helps make that easier, but sometimes you find out things like WhenAll only raises the first exception and have to implement something custom.

If you have any thoughts or know a better way to work with WhenAll, I’d love to hear them.

Gist of Full Example

Categories