---
title: "Missing Stack trace when eliding the await keyword"
description: "You may have heard that when you elide the `await` keyword in a method that returns a `Task` or `Task<T>,` you lose the stack trace. Buy why does that happen? Let's find out!"
updated: 2023-06-25
tags: ["Async", ".NET", "C#"]
canonical: "https://steven-giesel.com/blogPost/aa63a951-e619-4a31-9baf-5a15eb802b93/missing-stack-trace-when-eliding-the-await-keyword"
---

# Missing Stack trace when eliding the await keyword

You may have heard that when you elide the `await` keyword in a method that returns a `Task` or `Task<T>,` you lose the stack trace. Buy why does that happen? Let's find out!

## An example
Have a look at the following code:

```csharp
using System;
using System.Threading.Tasks;

try
{
    await DoWorkWithoutAwaitAsync();
}
catch (Exception e)
{
    Console.WriteLine(e);
}

static Task DoWorkWithoutAwaitAsync()
{
    return ThrowExceptionAsync();
}

static async Task ThrowExceptionAsync()
{
    await Task.Yield();
    throw new Exception("Hey");
}
```

The result of running this code is:

```no-class
System.Exception: Hey  
 at Program.<<Main>$>g__ThrowExceptionAsync|0_1()  
 at Program.<Main>$(String[] args)  
```

There is no trace of `DoWorkWithoutAwaitAsync` in the stack trace even though it is called. Let's discuss what happens here.

## The state machine
The only difference between `ThrowExceptionAsync` and `DoWorkWithoutAwaitAsync` is that the former does not use `await`. We can have a look at the translated code to see what happens:

```csharp
[System.Runtime.CompilerServices.NullableContext(1)]
[CompilerGenerated]
internal static Task <<Main>$>g__DoWorkWithoutAwaitAsync|0_0()
{
    return <<Main>$>g__ThrowExceptionAsync|0_1();
}

[System.Runtime.CompilerServices.NullableContext(1)]
[AsyncStateMachine(typeof(<<<Main>$>g__ThrowExceptionAsync|0_1>d))]
[CompilerGenerated]
internal static Task <<Main>$>g__ThrowExceptionAsync|0_1()
{
    <<<Main>$>g__ThrowExceptionAsync|0_1>d stateMachine = default(<<<Main>$>g__ThrowExceptionAsync|0_1>d);
    stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
    stateMachine.<>1__state = -1;
    stateMachine.<>t__builder.Start(ref stateMachine);
    return stateMachine.<>t__builder.Task;
}
```

The compiler has generated a state machine for `ThrowExceptionAsync` and not for the `DoWorkWithoutAwaitAsync` method. Let's have a look inside the state machines internal:

```csharp
try
{
    YieldAwaitable.YieldAwaiter awaiter;
    // Here is stuff
    awaiter.GetResult();
    throw new Exception("Hey");
}
catch (Exception exception)
{
    <>1__state = -2;
    <>t__builder.SetException(exception);
}
```

We are setting the task exception onto the `Task` object and don't throw anything up the stack (like an uncatched `throw`). The `Task` object is then awaited in the `Main` method, and the exception is thrown there.

Here is a small part about **stack traces**. **Stack traces** are not a picture **where** your code is coming from, but more where your code **is going to**. Now that seems weird. Well, for synchronous code, things are literally the same. Where you are coming from is where you will go back to. But with asynchronous code, that is different. And that is simple to show: If we have something like this:

```csharp
async Task DoAsyncWork()
{
    // Before
    await DoAsync();
    // After
}
```

We have a part after the `DoAsync` - the so called continuation. So once the `DoAsync` method is done, we will continue with the code after the `await`. While we are "waiting" for `DoAsync` we go back to the caller. That is why the stack trace is the same whether or not we look into the past or into the future! But now it gets interesting. If we have a method up the chain that does **not** await the method, it will just return and is basically done. So the stack trace will not contain the method that is not awaited. So once our `DoAsync` is done, the continuation continues and will point to the first caller that awaits `DoAsyncWork`. So everything in between that elided the `await` keyword is not part of the stack trace.

And I will quote here [Eric Lippert](https://stackoverflow.com/questions/15368309/stacktrace-stackframe-dont-return-expected-info-in-a-production-environment/15368508#15368508):
> No. A stack trace does not tell you where you came from in the first place. A stack trace tells you where you are going next. This is useful because there is often a strong correlation between where you came from and where you're going next; usually you're going back to where you came from.
>  
> This is not always true though. The CLR can sometimes figure out where to go next without knowing where you came from, in which case the stack trace doesn't contain the information you need.
>  
>  For example, tail call optimizations can remove frames from the stack. Inlining optimizations can make a call to a method look like part of the calling method. Asynchronous workflows in C# 5 completely divorce "where you came from" and "where you're going next"; the stack trace of an asynchronous method resumed after an await tells you where you are going after the next await, not how you got into the method before the first await.

We can see this if we follow the trail of our `async` function above. In the first iteration we have the following code:
```csharp
try
{
    await DoWorkWithoutAwaitAsync();  // -------------------
}                                    //                     |
catch (Exception e)                  //                     |
{                                    //                     |
    Console.WriteLine(e);            //                     |
}                                    //                     |
                                     //                     |
static Task DoWorkWithoutAwaitAsync()  // <----------------
    => ThrowExceptionAsync();

static async Task ThrowExceptionAsync()
{
    await Task.Yield();
    throw new Exception("Hey");
}
```

Nothing out of the ordinary. The next step looks like this:
```csharp
try
{
    await DoWorkWithoutAwaitAsync();
}
catch (Exception e)
{
    Console.WriteLine(e);
}

static Task DoWorkWithoutAwaitAsync() 
    => ThrowExceptionAsync();  // ------------------------
                               //                         |
static async Task ThrowExceptionAsync()  // <------------
{
    await Task.Yield();
    throw new Exception("Hey");
}
```

Now it gets interesting as we are approaching the `await`. What happens next is, that we are hitting the await boundary and give back control to the caller.

```csharp
try
{
    await DoWorkWithoutAwaitAsync();  // <-----------------
}                                    //                   |
catch (Exception e)                  //                   |
{                                    //                   |
    Console.WriteLine(e);            //                   |
}                                    //                   |
                               //                         |
static Task DoWorkWithoutAwaitAsync()  // ---------------
    => ThrowExceptionAsync();  // <-----------------------
                               //                         |
static async Task ThrowExceptionAsync()
{
    await Task.Yield();        // ------------------------
    throw new Exception("Hey");
}
```

We could see that we are passed `DoWorkWithoutAwaitAsync` as it just returns the `ThrowExceptionAsync` `Task`. As it returned entirely, it is gone from the stack trace. But wait, we are still awaiting `Task.Yield();`. So when `Task.Yield` is finally done, we are continuing with `throw new Exception("Hey");`. And guess where the continuation now is pointing to: Exactly, towards our `Main` method with the `try`-`catch` block. So even though in the past we did come from `DoWorkWithoutAwaitAsync` our stack trace doesn't have this information anymore! 

## Conclusion
To recap: Stack traces are a look into the future, not into the past. In 99% of cases, this might be the same, except if you elide the `await` keyword for example.