. NET Core 2.1 现在具有可读的异步堆栈信息!使得异步、迭代器和字典 ( key not found ) 中的堆栈更容易追踪!

这个大胆的主张意味着什么?

要知道,为了确定调用 异步 和 迭代器方法的实际重载,(这在以前)从堆栈信息中跟踪几乎是不可能的:

System.Collections.Generic.KeyNotFoundException: The given key '0' was not present in the dictionary.at System.Collections.Generic.Dictionary`2.get_Item(TKey key)at Program.Sequence(Int32 start)+MoveNext()at Program.Sequence(Int32 start, Int32 end)+MoveNext()at Program.MethodAsync()at Program.MethodAsync(Int32 v0)at Program.MethodAsync(Int32 v0, Int32 v1)at Program.MethodAsync(Int32 v0, Int32 v1, Int32 v2)at Program.MethodAsync(Int32 v0, Int32 v1, Int32 v2, Int32 v3)at Program.Main(String[] args)

问题: “使堆栈信息可读”

David Kean(@davkean) 于 2017 年 10 月 13 日在dotnet/corefx#24627 提出 使堆栈信息可读 的问题:

如今在 任务 (Task)、异步 (async) 和 等待 (await) 中普遍存在堆栈难以阅读的现象

对于在 .NET 中输出异步的可阅读堆栈信息已经梦魂萦绕了5年...

我直到 2017 年 10 月才意识到这个问题,好在 .NET Core 现在是完全开源的,所以我可以改变它。

作为参考,请参阅文章底部的代码,它将会输出如下的异常堆栈:

System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.   at System.ThrowHelper.ThrowKeyNotFoundException()   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)   at Program.<Sequence>d__8.MoveNext()   at Program.<Sequence>d__7.MoveNext()   at Program.<MethodAsync>d__6.MoveNext()--- End of stack trace from previous location where exception was thrown ---at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)   at Program.<MethodAsync>d__5.MoveNext()--- End of stack trace from previous location where exception was thrown ---at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)   at Program.<MethodAsync>d__4.MoveNext()--- End of stack trace from previous location where exception was thrown ---at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)   at Program.<MethodAsync>d__3.MoveNext()--- End of stack trace from previous location where exception was thrown ---at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)   at Program.<MethodAsync>d__2.MoveNext()--- End of stack trace from previous location where exception was thrown ---at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)   at Program.<Main>d__1.MoveNext()

(为简洁起见,删除了行号,如 in C:\Work\Exceptions\Program.cs:line 14

有时甚至可见更详细的胶水信息:

   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task) at System.Runtime.CompilerServices.TaskAwaiter.GetResult()

跟踪堆栈的一般用途是确定在源代码中发生错误的位置以及对应的路径。

然而,现如今我们无法避免异步堆栈,同时还要面对很多无用的噪声(干扰)。

PR: “隐藏请求中的异常堆栈帧 ”

堆栈信息通常是从抛出异常的地方直接输出的。

当异步函数抛出异常时,它会执行一些额外的步骤来确保响应,并且在延续执行(既定方法)之前会进行清理。

当这些额外的步骤被添加到调用堆栈中时,它们不会对我们确定堆栈信息有任何帮助,因为它们实际上是在出现异常 之后 执行。

所以它们是非常嘈杂和重复的,对于确定代码在哪里出现异常上并没有任何额外的价值。

实际产生的调用堆栈和输出的不一致:

在删除这些异常堆栈帧后(隐藏请求中的异常堆栈帧 dotnet/coreclr#14652 ),跟踪堆栈开始变得平易近人:

System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.at System.Collections.Generic.Dictionary`2.get_Item(TKey key)at Program.<Sequence>d__7.MoveNext()at Program.<Sequence>d__6.MoveNext()at Program.<MethodAsync>d__5.MoveNext()
--- End of stack trace from previous location where exception was thrown ---at Program.<MethodAsync>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---at Program.<MethodAsync>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---at Program.<MethodAsync>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---at Program.<MethodAsync>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---at Program.<Main>d__0.MoveNext()

并且输出的调用堆栈与实际的调用堆栈一致: 

PR: “删除异步的 Edi 边界”

异步中的异常使用 ExceptionDispatchInfo 类传播,这意味着着在每个连接点都会有这样的边界信息:

--- End of stack trace from previous location where exception was thrown ---

这只是让你知道两部分调用堆栈已经合并,并且有个过渡。

它如此频繁地出现在异步中,增加了很多噪音,并没有任何附加价值。

在 删除异步的 Edi 边界 dotnet/coreclr#15781 后 所有的 堆栈信息变得有价值:

System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.at System.Collections.Generic.Dictionary`2.get_Item(TKey key)at Program.<Sequence>d__7.MoveNext()at Program.<Sequence>d__6.MoveNext()at Program.<MethodAsync>d__5.MoveNext()at Program.<MethodAsync>d__4.MoveNext()at Program.<MethodAsync>d__3.MoveNext()at Program.<MethodAsync>d__2.MoveNext()at Program.<MethodAsync>d__1.MoveNext()at Program.<Main>d__0.MoveNext()

PR: “处理迭代器和异步方法中的堆栈”

在上一节中,堆栈已经是干净了,但是要确定是什么情况,还是很困难的一件事。

堆栈中包含着由 C# 编译器创建的异步状态机的基础方法签名,而不仅仅是(你的)源代码产生的。

你可以确定方法的名称,但是如果不深入挖掘,则无法确定所调用的实际重载。

在 处理迭代器和异步方法中的堆栈 dotnet/coreclr#14655 之后,堆栈更接近原始来源:

System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.at System.Collections.Generic.Dictionary`2.get_Item(TKey key)at Program.Sequence(Int32 start)+MoveNext()at Program.Sequence(Int32 start, Int32 end)+MoveNext()at Program.MethodAsync()at Program.MethodAsync(Int32 v0)at Program.MethodAsync(Int32 v0, Int32 v1)at Program.MethodAsync(Int32 v0, Int32 v1, Int32 v2)at Program.MethodAsync(Int32 v0, Int32 v1, Int32 v2, Int32 v3)at Program.Main(String[] args)

PR: “实现 KeyNotFoundException 的堆栈追踪”

因为有额外的奖励,我着手实现抛出 “ KeyNotFoundException ” 的堆栈追踪。

Anirudh Agnihotry (@Anipik) 提出了 实现 KeyNotFoundException 的堆栈追踪dotnet/coreclr#15201

这意味着这个异常现在要告诉你哪个 key 找不到的信息:

System.Collections.Generic.KeyNotFoundException: The given key '0' was not present in the dictionary.at System.Collections.Generic.Dictionary`2.get_Item(TKey key)at Program.Sequence(Int32 start)+MoveNext()at Program.Sequence(Int32 start, Int32 end)+MoveNext()at Program.MethodAsync()at Program.MethodAsync(Int32 v0)at Program.MethodAsync(Int32 v0, Int32 v1)at Program.MethodAsync(Int32 v0, Int32 v1, Int32 v2)at Program.MethodAsync(Int32 v0, Int32 v1, Int32 v2, Int32 v3)at Program.Main(String[] args)

支持的运行时以及相关进展

这些改进将在稍晚的时间发布到 Mono 上,并在下一个阶段发布。但是如果您使用的是较早的运行时版本 (.NET Core 1.0 - 2.0; .NET Framework 或 Mono) 想要获得一样的效果,需要使用 Ben.Demystifier 提供的Nuget 包,并且在你的异常中使用 .Demystify() 的方法:

catch (Exception e)
{Console.WriteLine(e.Demystify());
}

这些改进将会产生与 C#相得映彰的输出信息,最令人高兴的还是全都会被内置!

System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.at TValue System.Collections.Generic.Dictionary<TKey, TValue>.get_Item(TKey key)at IEnumerable<int> Program.Sequence(int start)+MoveNext()at IEnumerable<int> Program.Sequence(int start, int end)+MoveNext()at async Task<int> Program.MethodAsync()at async Task<int> Program.MethodAsync(int v0)at async Task<int> Program.MethodAsync(int v0, int v1)at async Task<int> Program.MethodAsync(int v0, int v1, int v2)at async Task<int> Program.MethodAsync(int v0, int v1, int v2, int v3)at async Task Program.Main(string[] args)

.NET Core 2.1 将成为 .NET Core 的最佳版本,原因说不完,这只是变得更美好的一小步...

上面提到的触发异常的代码及对应的堆栈信息

class Program{    static Dictionary<int, int> _dict = new Dictionary<int, int>();     static async Task Main(string[] args)    {            try{                  var value = await MethodAsync(1, 2, 3, 4);Console.WriteLine(value);}               catch (Exception e){Console.WriteLine(e);}}    static async Task<int> MethodAsync(int v0, int v1, int v2, int v3)        => await MethodAsync(v0, v1, v2);     static async Task<int> MethodAsync(int v0, int v1, int v2)        => await MethodAsync(v0, v1);      static async Task<int> MethodAsync(int v0, int v1)        => await MethodAsync(v0);       static async Task<int> MethodAsync(int v0)        => await MethodAsync();      static async Task<int> MethodAsync()    {           await Task.Delay(1000);             int value = 0;           foreach (var i in Sequence(0, 5))        {                value += i;}        return value;}   

    static IEnumerable<int> Sequence(int start, int end)    {            for (var i = start; i <= end; i++){                   foreach (var item in Sequence(i))            {                         yield return item;}}}      static IEnumerable<int> Sequence(int start)    {                              var end = start + 10;               for (var i = start; i <= end; i++){_dict[i] = _dict[i] + 1; // Throws exceptionyield return i;}}
}

原文地址:https://www.cnblogs.com/chenug/p/8401356.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

.NET Core 2.1中改进的堆栈信息相关推荐

  1. gdb 命令_gdb实用的调试技巧:启动方式、堆栈信息、单步调试

    对于很多开发者来说,开发过程中难免会遇到各种各样的bug, 所以,每个开发者应该考虑如何快速高效定位问题原因,而gdb是linux上很实用的调试工具,熟练掌握其调试技巧,将有助于提高解决问题的效率,也 ...

  2. Java打印完整的堆栈信息

    Java print full StackTrace 我们在编写一些组件时,使用的日志系统有时并不能打印完整的堆栈信息,比如slf4j,log4j,我们在调用log.error("found ...

  3. .NET/.NET Core中更清晰的堆栈跟踪

    在基于异常的语言中,堆栈跟踪是用于诊断问题最重要的工具之一.在某些情况下,开发人员能得到的仅为一条简短的错误信息以及堆栈跟踪,尤其是当个人可识别信息(PII)约束限制了日志记录的内容时. 随着任务并行 ...

  4. net应用程序中发生了未经处理的异常怎么办_介绍一些在.NET Core 3.0中引入的诊断改进工具...

    编者按:即使.NET Core3.1.5已经发布,在进行.NET Core的性能诊断时,我们有时依然不知该从何处下手,那这篇介绍.NET Core3.0中引入的诊断工具,或许能为我们提供参考. 在.N ...

  5. .NET Core 3.0 中的新变化

    译者:楚人Leo 译文:http://www.cnblogs.com/leolion/p/10585834.html 原文:https://msdn.microsoft.com/en-us/magaz ...

  6. 过滤日志中不相关的堆栈跟踪行

    我喜欢堆栈痕迹. 不是因为我喜欢错误,而是因为发生错误的那一刻,堆栈跟踪是无价的信息源. 例如,在Web应用程序中,堆栈跟踪向您显示完整的请求处理路径,从HTTP套接字到过滤器,Servlet,控制器 ...

  7. 堆栈跟踪 堆栈跟踪_过滤日志中无关的堆栈跟踪行

    堆栈跟踪 堆栈跟踪 我喜欢堆栈痕迹. 不是因为我喜欢错误,而是因为发生错误的那一刻,堆栈跟踪是无价的信息源. 例如,在Web应用程序中,堆栈跟踪向您显示完整的请求处理路径,从HTTP套接字到过滤器,S ...

  8. 通过gdb core dump方法查看程序异常时的堆栈信息

    在Linux下可通过core文件来获取当程序异常退出(如异常信号SIGSEGV, SIGABRT等)时的堆栈信息.core dump叫做核心转储,当程序运行过程中发生异常的那一刻的一个内存快照,操作系 ...

  9. 在 .NET Core 3.0 中实现 JIT 编译的 JSON 序列化,及一些心得与随想

    源码:https://github.com/Martin1994/JsonJitSerializer NuGet:https://www.nuget.org/packages/MartinCl2.Te ...

最新文章

  1. 规格选项表管理之保存规格选项表数据
  2. AI 世界的硬核之战,Tengine 凭什么成为最受开发者欢迎的主流框架?
  3. GARFIELD@12-29-2004
  4. sphinx是支持结果聚类的——WHERE、ORDER BY和GROUP BY
  5. ajax 延迟显示加载中提示
  6. Service OS maintain ( Reference Object )
  7. Linux 添加alisa,Linux/shell命令的实际应用——alias增加自定义命令
  8. Epic Games创始人眼中的虚拟现实
  9. 50Hz工频干扰消除
  10. 69个网盘搜索引擎资源(最全)
  11. at24c256读写linux,AT24C256写不进去
  12. 从功夫胖挞看软件开发
  13. MTPA 永磁同步电机 计算
  14. 美化导航html,HTML-美化
  15. kernel网络之RSS,RPS,RFS和XPS
  16. Python从入门到实践第9章课后作业
  17. 升级宽带到500M光纤
  18. matlab怎么仿真低频振荡,电力系统低频振荡MATLAB仿真毕业论文.doc
  19. MySQL查询满足条件的连续时间段
  20. 微信内置浏览器缓存清理及关闭

热门文章

  1. iOS 玩转CocoaPods
  2. IDC:第一季度全球服务器市场收入下滑4.6% 市场为重大升级做准备
  3. css 中图片旋转,倾斜,位移,平滑
  4. 宏在使用过程余函数的区别1
  5. Spark 1.2 发布,开源集群计算系统
  6. grep -A :显示匹配行和之后的几行
  7. 基于电子邮件的InfoPath表单发布的注意点 [Infopath 2007]
  8. BCVP开发者社区2022专属周边第一弹
  9. 如何定义一个 Listint,string 这样的 List ?
  10. 持续20年,一场威胁Linux存亡的诉讼终结束