使用.NETasynchronous方法可以获得一个好的堆栈跟踪吗?

我在WebApi应用程序中安装了以下示例代码:

[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!"); } 

不幸的是,当GetValueAction被命中时,返回的堆栈跟踪是:

  " 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" 

因此,我得到(损坏)跟踪中的GetValue2和GetValue,但没有提到GetValueAction。 难道我做错了什么? 有没有另外一种模式可以让我获得更完整的堆栈跟踪?

编辑:我的目标不是编写依赖于堆栈跟踪的代码,而是使asynchronous方法中的失败更容易debugging。

首先,堆栈跟踪不符合大多数人的想法。 它们在debugging过程中可能很有用,但不适合运行时使用,特别是在ASP.NET上。

另外,堆栈跟踪在技术上关于代码返回的位置 ,而不是代码的来源 。 使用简单的(同步)代码,两者是相同的:代码总是返回到任何称为它的方法。 但是,对于asynchronous代码,这两者是不同的。 堆栈跟踪再一次告诉你接下来会发生什么,但是你对过去发生的事情感兴趣。

所以,堆栈框架不是你的需要的正确答案。 Eric Lippert在他的回答中解释了这一点 。

@ColeCampbell链接到的MSDN文章描述了使用async代码跟踪“散列链”(代码来自哪里)的一种方法。 不幸的是,这种方法是有限的(例如,它不处理分叉/join场景); 但是,这是我知道的唯一方法在Windowsapp store应用程序中起作用。

由于您使用的是完整的.NET 4.5运行时ASP.NET,因此您可以使用更强大的解决scheme来跟踪临时性链:逻辑调用上下文。 你的async方法必须“join”,所以你不会像堆栈跟踪一样免费获得它。 我只是写了一篇尚未发表的博客文章,所以你正在预览。 🙂

您可以围绕逻辑调用上下文自己构build一个“堆栈”,如下所示:

 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 在这里可用)。 你可以像这样使用它:

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

这种方法的好处是,它可以处理所有的 async代码:fork / join,自定义awaitables, ConfigureAwait(false)等。缺点是你增加了一些开销。 此外,这种方法只适用于.NET 4.5 ; .NET 4.0上的逻辑调用上下文不是async并且不能正常工作。

更新:我发布了一个NuGet包(在我的博客中描述) ,它使用PostSharp自动注入压入和popup。 所以现在做一个好的跟踪应该会简单得多。

asynchronous/等待国王有一个很好的nuget扩展。

https://www.nuget.org/packages/AsyncStackTraceEx/

你需要改变你的等待电话

 Await DownloadAsync(url) 

 Await DownloadAsync(url).Log() 

最后,在catch块,只是打电话

 ex.StackTraceEx() 

一个重要的注意事项:这个方法只能被调用一次,ex.StackTrace之前不能被评估。 看来这个堆栈只能被读取一次。