我在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。难道我做错了什么?是否有另一种模式可以让我获得更完整的堆栈跟踪?
编辑:我的目标不是编写依赖于堆栈跟踪的代码,而是让异步方法中的失败更容易调试。
答案 0 :(得分:36)
首先,堆栈跟踪不会像大多数人认为的那样做。它们在调试期间非常有用,但不适合运行时使用,特别是在ASP.NET上。
此外,堆栈跟踪在技术上是关于代码返回的,而不是代码来自的。使用简单(同步)代码,两者是相同的:代码总是返回到任何称为它的方法。但是,对于异步代码,这两者是不同的。同样,堆栈跟踪会告诉您 next 会发生什么,但您对过去中发生的事情感兴趣。
因此,堆栈帧不是满足您需求的正确答案。 Eric Lippert explains this well in his answer here
@ColeCampbell链接的MSDN article描述了一种使用async
代码跟踪“临时链”(代码来自)的方法。不幸的是,这种方法是有限的(例如,它不处理fork / join方案);但是,这是我所知道的唯一可以在Windows应用商店应用中运行的方法。
由于您使用完整的.NET 4.5运行时使用ASP.NET,因此您可以访问用于跟踪临时链的更强大的解决方案:逻辑调用上下文。但是,您的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
可用here)。然后你可以像这样使用它:
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 package (described on my blog),它使用PostSharp自动注入推送和弹出。因此,现在获得良好的追踪应该更加简单。
答案 1 :(得分:6)
该问题及其获得最高投票的答案写于2013年。此后,情况有所改善。
.NET Core 2.1现在提供了可理解的异步堆栈跟踪。参见Stacktrace improvements in .NET Core 2.1。
对于仍然使用.NET Framework的用户,有一个出色的NuGet软件包,用于修复堆栈跟踪中的异步(以及许多其他隐晦之处):Ben.Demystifier。与其他建议相比,此软件包的优势在于它不需要更改抛出代码或汇编。您只需在捕获到的异常上调用Demystify
或ToStringDemystified
。
将此应用于您的代码:
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 all the way的精神),您将获得预期的干净结果:
System.InvalidOperationException: Couldn't get value!
at async Task<double> ValuesController.GetValue2()
at async Task<double> ValuesController.GetValue()
at async Task<double> ValuesController.GetValueAction()
答案 2 :(得分:4)
async / await king有一个很好的nuget扩展名。
https://www.nuget.org/packages/AsyncStackTraceEx/
您需要更改等待来自
的电话Await DownloadAsync(url)
到
Await DownloadAsync(url).Log()
最后,在catch块中,只需调用
ex.StackTraceEx()
一个重要注意事项:此方法只能调用一次,之前不得评估ex.StackTrace。看起来堆栈只能读一次。