使用.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之前不能被评估。 看来这个堆栈只能被读取一次。
- 拒绝在框架中显示,因为它将“X-Frame-Options”设置为“SAMEORIGIN”
- 如何在Web API 2中存储服务器端的持票人代币?
- Web API 2:如何使用camelCased属性名称,对象及其子对象返回JSON
- 通过隧道(使用ngrok)将localhost暴露给互联网:HTTP错误400:错误的请求; 无效的主机名
- 有推荐的方法来使用ASP.NET Web API返回图像
- 用邮差发送嵌套的json对象
- 当ID包含句点时,ApiController返回404
- ASP.NET Web API中具有多个GET方法的单个控制器
- 为什么我的ASP.NET Web API ActionFilterAttribute的OnActionExecuting不能触发?