我有一个应用程序的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的。 难道我做错了什么? 有没有会让我更完整的堆栈跟踪另一种模式?
编辑:我的目标是不写代码依靠堆栈跟踪,而是在异步方法,使故障更容易调试。
这个问题和它的最高投票的答案写回在2013年的事情从那以后得到了改善。
.NET核心2.1现在提供可理解的异步栈跟踪开箱; 看到在.NET 2.1的核心改进堆栈跟踪 。
对于那些仍然在.NET框架中,有能解决了在堆栈跟踪异步(和许多其他晦涩)优良的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
方法异步(在精神异步一路 ),你会得到预期的结果,清洁:
System.InvalidOperationException: Couldn't get value!
at async Task<double> ValuesController.GetValue2()
at async Task<double> ValuesController.GetValue()
at async Task<double> ValuesController.GetValueAction()
首先,堆栈跟踪不这样做大多数人认为他们这样做。 他们可以在调试过程中非常有用,但不打算使用运行时,特别是在ASP.NET。
此外,堆栈跟踪在技术上是哪里的代码返回 ,不要在那里的代码来自 。 通过简单的(同步)代码,两者是相同的:代码总是返回到任何方法的说法。 然而,随着异步代码,这两个是不同的。 再次,堆栈跟踪告诉你接下来会发生什么,但你有兴趣在过去发生的事情。
因此,堆栈帧是不是您需要的正确答案。 埃里克利珀这里解释了这口井在他的回答 。
该MSDN文章说@ColeCampbell链接到介绍追踪“因果链”(其中的代码是从哪里来的 )一个方式async
代码。 不幸的是,这种方法受到限制(例如,它不处理叉/加入的情况); 然而,这是我所知道的是,在Windows应用商店中的应用程序不工作的唯一办法。
既然你在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
代码:叉子/加入,自定义awaitables, ConfigureAwait(false)
,等等缺点是您要添加一些开销。 此外,这种方法只适用于.NET 4.5; 在.NET 4.0的逻辑调用上下文不是async
知晓的,并且将无法正常工作。
更新:我发布的NuGet包(在我的博客中描述)使用PostSharp注入推送和自动弹出。 因此,获得良好的痕迹应该是简单得多了。
有一个很好的NuGet扩展这个由异步/王的await。
https://www.nuget.org/packages/AsyncStackTraceEx/
您需要更改您的await通话
Await DownloadAsync(url)
至
Await DownloadAsync(url).Log()
最后,在catch块,只需调用
ex.StackTraceEx()
一个重要的注意事项:此方法只能被调用一次,ex.StackTrace不得早评估。 看来,堆栈只能读一次。