TL;DR

Programming with an asynchronous operation is always trickier than a classic syncrhonous sequential style. It’s a no news.

C# offers the async await construct. While it is quite simple to use, as a developper you must be careful with it for it plays heavily with the execution flow.

The wisest way to go about it is to take some time to understand it thoroughly and foresee the common pitfalls. I advise you to do the following before using it:

  • Read the MSDN documentation 1
  • Acknowledge the danger of async void 2
  • Bookmark this conference and follow it with concentration 3

A crappy introduction

Let me tell you a story about a developper and his habits. A creature who takes pride in writing concise code and feels shame when he produces circumvoluted designs. A story of an everyday, regular, normal dev

Our guy being originally a java developer, he felt like giving a go writing some C# for one of his projects.

I will simplify his thoughts for your comfort, hence these examples are just examples, do not try this on a production project

Who likes writing try catch?

An old habit of our friend is to wrap his error handling within simple constructs, so that instead of repeating try { ... } catch { ... } like this

try {
    VeryCleverComputationThatNobodyUnderstand();
} catch(Exception) {
    Console.WriteLine("I am too talented, we do not need those pesky exceptions, let's ignore it !");
}

He prefers to write repetitive constructs like this:

var anInteger = Silently(ComputeAValueInAComplicatedManner(), 0); // We get a value no matter what
Silently(SideEffectWhichCouldGoWrongInAlotOfWays()); // Succeed or fail, I don't mind
public static T Silently<T>(Func<T> expression, T fallback)
{
    try
    {
        return expression();
    }
    catch (Exception)
    {
        Console.WriteLine("Ignoring exception.");
    }

    return fallback;
}

public static void Silently(Action statement)
{
    try
    {
        statement();
    }
    catch (Exception)
    {
        Console.WriteLine("Ignoring exception.");
    }
}

This routine code has been a good companion of our fellow developer for quite some time now, rarely failed him and when used with frugality, is quite an awesome pattern!

Different ways to say the same thing

Elaborating on our last pattern, let’s consider our friend’s code:

int TwoMayCrash(bool crash = true)
{
    if (crash)
        throw new InvalidOperationException();
    return 2;
}

int Two() => TwoMayCrash(false);
int TwoCrash() => TwoMayCrash();

Action Effect = () => Console.WriteLine("Computed effect");
Action EffectCrash = () => throw new InvalidOperationException();

...

Console.WriteLine("============ Simple expressions.");
Assert(Silently(Two, 0) == 2, "Most basic example, should return 2");
Assert(Silently(TwoCrash, 0) == 0, "Here, the expression fails, we fallback.");
Console.WriteLine("============");

Console.WriteLine("============ Simple effects.");
Silently(Effect); // Print that effect took place
Silently(EffectCrash); // Print that effect failed, silently ignore the exception
Console.WriteLine("============");

Nothing too surprising I hope. Now that we are familiarized with this, let’s try to compose it with some asynchronous code

With asynchronous code

Let’s consider this

async Task<int> TwoAsync()
{
    return await Task.Run(Two);
}

async Task<int> TwoCrashAsync()
{
    return await Task.Run(TwoCrash);
}

async Task EffectAsync()
{
    await Task.Run(Effect);
}

async Task EffectCrashAsync()
{
    await Task.Run(EffectCrash);
}

Now, our friend have a way to compute the really resource hungry value of 2 and Console.WriteLine asynchronously ( ͡° ͜ʖ ͡°)

Composition with Silently

Now comes the time that require your attention. The following examples are flawed and don’t work as expected even though it appear to do so. Let’s begin

await Silently(EffectAsync, Task.CompletedTask);
int two = await Silently(TwoAsync, Task.FromResult(0));

It behaves as expected, our friend sees Computed effect printing on the console and gets the value 2. The computation runs and we even fallback to a completed task if anything goes wrong (does it work as expected though ? Let’s pretend so)

Speaking of something going wrong, let’s try these ones:

await Silently(EffectCrashAsync, Task.CompletedTask);
int two = await Silently(TwoCrashAsync, Task.FromResult(0));

Our friend should not get anything printed on the console, retrieve 0 as a fallback result and should not get any crash. Really ?

In fact, he gets an uncatched InvalidOperationException instead. But why ?! (ノಥ益ಥ)ノ彡┻━┻)

The world collapses, here is why

What can he do if we cannot even trust a good old try {} catch(Exception) {} ! Let’s think this through with him.

This has to do with asynchronous computation obviously. We might be using the async await construct incorrectly. Let’s review it:

MSDN documentation says the following about await 4:

The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. When the asynchronous operation completes, the await operator returns the result of the operation, if any. When the await operator is applied to the operand that represents already completed operation, it returns the result of the operation immediately without suspension of the enclosing method. The await operator doesn’t block the thread that evaluates the async method. When the await operator suspends the enclosing async method, the control returns to the caller of the method.

And this about async 5 :

Use the async modifier to specify that a method, lambda expression, or anonymous method is asynchronous. If you use this modifier on a method or expression, it’s referred to as an async method. […] An async method runs synchronously until it reaches its first await expression, at which point the method is suspended until the awaited task is complete. In the meantime, control returns to the caller of the method, as the example in the next section shows.

Put differently:

  • await is enclosed to an async method, also called the enclosing asynchronous operation
  • await also represents an asynchronous operation
  • when used, await suspends the enclosing asynchronous operation until its proper asynchronous operation finished
  • await suspends its enclosing asynchronous operation but does not block the thread running it
  • async is used to mark diverse constructs as being asynchronous compatible (method, lambda…)
  • async runs synchronously until it encounters an await operation
  • when async method encounters an await operation, it suspends its own execution until await finished.
  • while suspending its execution the async method ‘returns’ the execution flow to the caller. ‘taking back’ the execution flow when the await operation finishes

Not appearing in the excerpts but also true:

  • await shall only appear within an async method.
  • await shall only work on a Task
  • Method marked with async should return a Task or void

async void is allowed to be compatible with event handling. It is generally strongly discouraged to use it because it can lead to the same kind of problems we are having now (though async void is not the cause in our case). This article 2 address the async void issue in a more detailed manner.

Knowing these elements, we could picture async await as a chain. A chain we should not be breaking since it acts heavily with the execution flow of our program. We have a lead, let’s see if the chain is broken.

Let’s review our problematic call

await Silently(EffectAsync, Task.CompletedTask);

Knowing the definition of silently:

public static T Silently<T>(Func<T> expression, T fallback)
{
    try
    {
        return expression();
    }
    catch (Exception)
    {
        Console.WriteLine("Ignoring exception.");
    }

    return fallback;
}

Is equivalent of writing:

public static Task SilentlyEffectCrashAsync() {
    try {
        return EffectCrashAsync();
    } catch (Exception e) {
        Console.WriteLine("Ignoring exception.");
    }
    return Task.CompletedTask;
}

But wait a minute, EffectCrashAsync is an async task which is not awaited, nor is the call encapsulated in an enclosing asynchronous operation thanks to async keyword !

That’s it. We broke the chain when we encapsulate our async operation in Silently. What happens in this case then ? We actually performed a fire and forget call. equivalent to this:

try {
    return Task.Run(() => new InvalidOperationException());
} catch(Exception e) {
    Console.WriteLine("Ignoring exception.");
}
return Task.CompletedTask;

What we try { ... } catch { ... } was actually the ‘launching’ of the task, not its proper execution, thus the exception happening in the execution of the task is unhandled ! Without compiler warning !

Conclusion

We leave the reader the freedom to find how to fix the issue, it’s not the point of the article ;)

Asynchronous programming is always trickier than the synchronous and sequential world. The async await tool is actually great for writing more concise code. The only piece of advice I would give is to fully comprehend this construction before using it in a serious project.

Here is the source code illustrating the article if you want to tinker with it:

Here are some useful resources: