Eliding await keyword

How and why the await keyword should be elided

Home DailyDrop

Daily Knowledge Drop

When multiple async method are called in a sequence, the async methods should be elided and the Tasks should be awaited and not passed up the call stack.

If a Task is passed up the stack, and an exception occurs - the Task which is not awaited will not be part of the error stack trace.


No await exception

In the below code snippet, we have a call stack where a Task is not awaited immediately, but passed up the call stack to be awaited:

await CallStackStart();

static async Task CallStackStart()
{
    try
    {
        // call a method which returns a task
        await NoAwaitMethod();
    }
    catch (Exception e)
    {
        Console.WriteLine("Stacktrace for the exception:");
        Console.WriteLine(e);
    }
}

// return a Task
static Task NoAwaitMethod()
{
    // call a method which returns a Task but 
    // do NOT await
    return ThrowExceptionAsync();
}

static async Task ThrowExceptionAsync()
{
    await Task.Delay(1);
    throw new Exception("Manual exception has been thrown");
}

Starting from the bottom of the call stack:

  • ThrowExceptionAsync is an async method in which an exception could (will in this example) be thrown.
  • NoAwaitMethod calls into ThrowExceptionAsync, but does not await the call. The method returns the Task returned from ThrowExceptionAsync
  • CallStackStart calls into NoAwaitMethod and awaits the Task the method returns - which is the Task returned from ThrowExceptionAsync.

Running the above code, the call stack generated from the exception is:

Stacktrace for the exception:
System.Exception: Manual exception has been thrown
   at Program.<<Main>$>g__ThrowExceptionAsync|0_2() in C:\Development\Blog\ElideAwait\Program.cs:line 28
   at Program.<Main>$(String[] args) in C:\Development\Blog\ElideAwait\Program.cs:line 5

The stack trace has no mention that the call stack went through the NoAwaitMethod method!. This is due to the fact that the method is basically just a pass through method for the Task.

To get a more accurate stack trace, the async method needs to be awaited.


await exception

As mentioned above, instead of a method like NoAwaitMethod, which serves as a pass through for the Task, the Task should be awaited:

wait CallStackStart();

static async Task CallStackStart()
{
    try
    {
        // call a method which returns a task
        await AwaitMethod();
    }
    catch (Exception e)
    {
        Console.WriteLine("Stacktrace for the exception:");
        Console.WriteLine(e);
    }
}

static async Task AwaitMethod()
{
    // await the task returned instead
    // of just returning the Task
    await ThrowExceptionAsync();
}

static async Task ThrowExceptionAsync()
{
    await Task.Delay(1);
    throw new Exception("Manual exception has been thrown");
}

Again, starting from the bottom of the call stack:

  • ThrowExceptionAsync is an async method in which an exception could (will in this example) be thrown.
  • AwaitMethod calls into ThrowExceptionAsync, and awaits the call.
  • CallStackStart calls into NoAwaitMethod and awaits the Task the method returns.

Now running the above code, the full complete stack trace is part of the exception:

Stacktrace for the exception:
System.Exception: Manual exception has been thrown
   at Program.<<Main>$>g__ThrowExceptionAsync|0_2() in C:\Development\Blog\ElideAwait\Program.cs:line 28
   at Program.<<Main>$>g__AwaitMethod|0_1() in C:\Development\Blog\ElideAwait\Program.cs:line 21
   at Program.<Main>$(String[] args) in C:\Development\Blog\ElideAwait\Program.cs:line 6 

This time we have a full, complete stack trace!


Notes

When await async is used in conjunction with any exceptions, the Tasks in question should be elided and awaited and not passed up the stack trace as it can cause certain methods to be omitted from exception stack traces.


References

Elide await keyword - Exceptions

Daily Drop 238: 19-01-2023

At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.

The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.
c# .net async await elide