Concurrency (asynchronous programming, if we have to be more specific) is blessing to us, developers. However, we do not want to await every async function call. We want to fire and forget the action, such as sending a notification. There is not much reason to wait the notification to get send. Still, the exceptions are needed to be handled.
Take a look at the following code. Foo function takes 20 seconds to execute and does not affect the flow of the the rest of the execution.
try
{
await Foo();
}
catch
{
Console.Write("This does not work");
}
This codeblock works as expected. We handle any exception that is thrown by Foo function.
The problem
Let’s remove the await operator so that it is not blocking. However, this time catch block cannot catch any exception that is thrown by Foo. Because, during the execution of the catch block, it is most likely that the execution of the Foo function hasn’t started yet. If Foo raises any exception, it will break the program since the catch block that is supposed to handle is left behind.
Solution
An async function call has a return type of System.Threading.Tasks.Task which has a .ContinueWith() method.
try
{
Foo().ContinueWith(task =>
{
Console.Write("This does not work");
})
}
catch
{
Console.Write("This does not work");
}
Now, after finishing the execution of the Foo, “This does not work” will be printed. We can use the Task.IsFaulted property to understand if the Foo threw an exception. We can also remove the try-catch block since exceptions are handled inside the ContinueWith() method.
Foo().ContinueWith(task =>
{
if (task.IsFaulted)
Console.Write("This does not work");
});
Passing parameters to ContinueWith() method
What if we call bunch of async functions and we want to identify which call threw an exception?
for (var i = 0; i < 25; i++)
{
Foo(i).ContinueWith(task =>
{
Console.Write(i);
Console.Write("th call threw an exception");
})
}
Above code will not work because, by the time .ContinueWith() is called, i will have been updated multiple times. It is most likely that .ContinueWith() read only 25.
Closures to the rescue
In the previous example, all the .ContinueWith() calls shared a single closure, which is the function closure which has a single variable, i.
So, we use a foreach loop instead of a basic for loop. foreach loop creates a new closure for every iteration of the loop, unlike the for loop. This means that by the time the loop is finished, there are 25 closures and 25 different variable i’s. Consequently, each .ContinueWith() method call accesses to a unique i, dedicated to itself.
foreach (var i in numbers)
{
Foo(i).ContinueWith(task =>
{
Console.Write(i);
Console.Write("th call threw an exception");
})
}
