Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to get a good stack trace with .NET async methods?

I have the following sample code setup in a WebApi application:

[HttpGet] public double GetValueAction() {     return this.GetValue().Result; }  public async Task<double> GetValue() {     return await this.GetValue2().ConfigureAwait(false); }  public async Task<double> GetValue2() {     throw new InvalidOperationException("Couldn't get value!"); } 

Sadly, when GetValueAction gets hit, the stack trace that comes back is:

    " at MyProject.Controllers.ValuesController.<GetValue2>d__3.MoveNext() in c:\dev\MyProject\MyProject\Controllers\ValuesController.cs:line 61 --- End of stack trace from previous location where exception was thrown ---  at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)  at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult() at MyProject.Controllers.ValuesController.<GetValue>d__0.MoveNext() in c:\dev\MyProject\MyProject\Controllers\ValuesController.cs:line 56" 

Thus, I get (mangled) GetValue2 and GetValue in the trace, but no mention of GetValueAction. Am I doing something wrong? Is there another pattern that will get me more complete stack traces?

EDIT: my goal is not to write code relying on the stack trace, but instead to make failures in async methods easier to debug.

like image 681
ChaseMedallion Avatar asked Mar 14 '13 13:03

ChaseMedallion


People also ask

What is async stack traces?

Asynchronous stack traces allow you to inspect function calls beyond the current event loop. This is particularly useful because you can examine the scope of previously executed frames that are no longer on the event loop. This feature is currently an experiment and needs to be enabled.

Is .NET core async?

NET Core C# we make use of async and await keywords to implement asynchronous programming. For any method to be asynchronous we have to add the async keyword in the method definition before the return type of the method. Also, it is general practice to add Async to the name of the method if that method is asynchronous.

Why we use async method in C#?

The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete.


1 Answers

First off, stack traces don't do what most people think they do. They can be useful during debugging, but are not intended for runtime use, particularly on ASP.NET.

Also, the stack trace is technically about where the code is returning to, not where the code came from. With simple (synchronous) code, the two are the same: the code always returns to whatever method called it. However, with asynchronous code, those two are different. Again, the stack trace tells you what will happen next, but you're interested in what happened in the past.

So, the stack frame is not the correct answer for your needs. Eric Lippert explains this well in his answer here.

The MSDN article that @ColeCampbell linked to describes one way to track "casuality chains" (where the code came from) with async code. Unfortunately, that approach is limited (e.g., it doesn't handle fork/join scenarios); however, it is the only approach I know of that does work in Windows Store applications.

Since you're on ASP.NET with the full .NET 4.5 runtime, you have access to a more powerful solution for tracking casuality chains: the logical call context. Your async methods do have to "opt in", though, so you don't get it for free like you would with a stack trace. I just wrote this up in a blog post that is not yet published, so you're getting a preview. :)

You can build a "stack" of calls yourself around the logical call context as such:

public static class MyStack {   // (Part A) Provide strongly-typed access to the current stack   private static readonly string slotName = Guid.NewGuid().ToString("N");   private static ImmutableStack<string> CurrentStack   {     get     {       var ret = CallContext.LogicalGetData(name) as ImmutableStack<string>;       return ret ?? ImmutableStack.Create<string>();     }     set { CallContext.LogicalSetData(name, value); }   }    // (Part B) Provide an API appropriate for pushing and popping the stack   public static IDisposable Push([CallerMemberName] string context = "")   {     CurrentStack = CurrentStack.Push(context);     return new PopWhenDisposed();   }   private static void Pop() { CurrentContext = CurrentContext.Pop(); }   private sealed class PopWhenDisposed : IDisposable   {     private bool disposed;     public void Dispose()     {       if (disposed) return;       Pop();       disposed = true;     }   }    // (Part C) Provide an API to read the current stack.   public static string CurrentStackString   {     get { return string.Join(" ", CurrentStack.Reverse()); }   } } 

(ImmutableStack is available here). You can then use it like this:

static async Task SomeWork() {   using (MyStack.Push())   {     ...     Console.WriteLine(MyStack.CurrentStackAsString + ": Hi!");   } } 

The nice thing about this approach is that it works with all async code: fork/join, custom awaitables, ConfigureAwait(false), etc. The disadvantage is that you're adding some overhead. Also, this approach only works on .NET 4.5; the logical call context on .NET 4.0 is not async-aware and will not work correctly.

Update: I released a NuGet package (described on my blog) that uses PostSharp to inject the pushes and pops automatically. So getting a good trace should be a lot simpler now.

like image 165
Stephen Cleary Avatar answered Sep 21 '22 23:09

Stephen Cleary