有可能得到一个良好的堆栈跟踪与.net异步方法

本文关键字:堆栈 跟踪 异步方法 net 一个 有可能 | 更新日期: 2023-09-27 18:03:58

我在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。我做错了什么吗?有没有另一种模式可以让我得到更完整的堆栈跟踪?

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

有可能得到一个良好的堆栈跟踪与.net异步方法

首先,堆栈跟踪并不像大多数人认为的那样。它们可能在调试期间有用,但不打算在运行时使用,特别是在ASP.NET上。

此外,堆栈跟踪技术上是关于代码返回到的,而不是代码来自。对于简单(同步)代码,这两者是相同的:代码总是返回到调用它的任何方法。然而,对于异步代码,这两者是不同的。同样,堆栈跟踪告诉您下一个将发生什么,但您感兴趣的是过去发生了什么。

所以,栈帧不是你需要的正确答案。Eric Lippert在他的回答中对此做了很好的解释。

@ColeCampbell链接到的MSDN文章描述了用async代码跟踪"因果链"(代码来自

的地方)的一种方法。不幸的是,这种方法是有限的(例如,它不能处理fork/join场景);然而,这是我所知道的唯一能在Windows Store应用程序中工作的方法。

既然你在ASP上。NET与完整的。NET 4.5运行时,您可以访问一个更强大的解决方案来跟踪因果链:逻辑调用上下文。但是,您的async方法必须"选择加入",因此您不会像使用堆栈跟踪那样免费获得它。我刚刚在一篇尚未发布的博客文章中写了这篇文章,所以你可以预览一下。:)

你可以在逻辑调用上下文周围构建一个调用"堆栈",如下所示:

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、自定义可等待对象、ConfigureAwait(false)等。缺点是增加了一些开销。而且,这种方法只适用于。net 4.5;. net 4.0中的逻辑调用上下文不是async感知的,并且不是将正确工作。

更新:我发布了一个NuGet包(在我的博客上描述),它使用PostSharp自动注入推送和弹出。因此,获得一个好的跟踪现在应该简单多了。

这个问题和得票最高的答案是在2013年写的。从那时起,情况有所改善。

。. NET Core 2.1现在提供了易于理解的异步堆栈跟踪;参见。net Core 2.1中Stacktrace的改进。

对于那些仍然使用。net框架的人来说,有一个很好的NuGet包可以修复堆栈跟踪中的异步(和许多其他模糊):Ben.Demystifier。与其他建议相比,这个包的优点是它不需要更改抛出代码或程序集;您只需在捕获的异常上调用DemystifyToStringDemystified

应用到你的代码:

System.AggregateException: One or more errors occurred. ---> System.InvalidOperationException: Couldn't get value!
   at async Task<double> ValuesController.GetValue2()
   at async Task<double> ValuesController.GetValue()
   --- End of inner exception stack trace ---
   at void System.Threading.Tasks.Task.ThrowIfExceptional(bool includeTaskCanceledExceptions)
   at TResult System.Threading.Tasks.Task<TResult>.GetResultCore(bool waitCompletionNotification)
   at TResult System.Threading.Tasks.Task<TResult>.get_Result()
   at double ValuesController.GetValueAction()
   at void Program.Main(string[] args)
---> (Inner Exception #0) System.InvalidOperationException: Couldn't get value!
   at async Task<double> ValuesController.GetValue2()
   at async Task<double> ValuesController.GetValue()<---

不可否认,由于使用Task<T>.Result,这仍然有点令人费解。如果您将GetValueAction方法转换为async(在异步的精神下),您将得到预期的干净结果:

System.InvalidOperationException: Couldn't get value!
   at async Task<double> ValuesController.GetValue2()
   at async Task<double> ValuesController.GetValue()
   at async Task<double> ValuesController.GetValueAction()

async/await国王对此有一个很好的nuget扩展。

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

你需要从

更改await调用
Await DownloadAsync(url)

Await DownloadAsync(url).Log()
最后,在catch块中,只需调用
ex.StackTraceEx()

一个重要的注意事项:此方法只能被调用一次,并且在此之前不能求值ex.StackTrace。