博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
.NET Core 2.1中改进的堆栈信息
阅读量:6084 次
发布时间:2019-06-20

本文共 9069 字,大约阅读时间需要 30 分钟。

原文 : 作者 : 译者 :

. 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() 于 2017 年 10 月 13 日在 提出 使堆栈信息可读 的问题:

如今在 任务 (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.
d__8.MoveNext() at Program.
d__7.MoveNext() at Program.
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.
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.
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.
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.
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.
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: “隐藏请求中的异常堆栈帧 ”

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

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

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

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

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

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

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.
d__7.MoveNext() at Program.
d__6.MoveNext() at Program.
d__5.MoveNext()--- End of stack trace from previous location where exception was thrown --- at Program.
d__4.MoveNext()--- End of stack trace from previous location where exception was thrown --- at Program.
d__3.MoveNext()--- End of stack trace from previous location where exception was thrown --- at Program.
d__2.MoveNext()--- End of stack trace from previous location where exception was thrown --- at Program.
d__1.MoveNext()--- End of stack trace from previous location where exception was thrown --- at Program.
d__0.MoveNext()复制代码

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

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

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

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

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

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

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

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.
d__7.MoveNext() at Program.
d__6.MoveNext() at Program.
d__5.MoveNext() at Program.
d__4.MoveNext() at Program.
d__3.MoveNext() at Program.
d__2.MoveNext() at Program.
d__1.MoveNext() at Program.
d__0.MoveNext()复制代码

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

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

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

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

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

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 () 提出了 实现 KeyNotFoundException 的堆栈追踪

这意味着这个异常现在要告诉你哪个 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) 想要获得一样的效果,需要使用  提供的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
.get_Item(TKey key) at IEnumerable
Program.Sequence(int start)+MoveNext() at IEnumerable
Program.Sequence(int start, int end)+MoveNext() at async Task
Program.MethodAsync() at async Task
Program.MethodAsync(int v0) at async Task
Program.MethodAsync(int v0, int v1) at async Task
Program.MethodAsync(int v0, int v1, int v2) at async Task
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
_dict = new Dictionary
(); 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
MethodAsync(int v0, int v1, int v2, int v3) => await MethodAsync(v0, v1, v2); static async Task
MethodAsync(int v0, int v1, int v2) => await MethodAsync(v0, v1); static async Task
MethodAsync(int v0, int v1) => await MethodAsync(v0); static async Task
MethodAsync(int v0) => await MethodAsync(); static async Task
MethodAsync() { await Task.Delay(1000); int value = 0; foreach (var i in Sequence(0, 5)) { value += i; } return value; } static IEnumerable
Sequence(int start, int end) { for (var i = start; i <= end; i++) { foreach (var item in Sequence(i)) { yield return item; } } } static IEnumerable
Sequence(int start) { var end = start + 10; for (var i = start; i <= end; i++) { _dict[i] = _dict[i] + 1; // Throws exception yield return i; } }}复制代码

本文采用 转载请注明来源:

转载于:https://juejin.im/post/5aaa0fc56fb9a028d566d5d3

你可能感兴趣的文章
STIX:一个网络空间威胁情报分享的标准
查看>>
基于盐+Sha算法的安全密码保护机制
查看>>
FAQ系列 | mysqldump选项之skip-opt
查看>>
SQL Server 查询 Active Directory(1)
查看>>
红帽混合云坚持开放
查看>>
什么是linux,linux的应用与发展
查看>>
使用Sqlite Select返回满足条件的第一条与最后一条记录
查看>>
Android应用程序启动过程源代码分析(5)
查看>>
SSHv1版本的crc32漏洞
查看>>
windows phone发布时其他注意事项
查看>>
一把手的态度决定产品质量
查看>>
用Proxmox搞定gpu穿透
查看>>
18个有趣的API供你的前端开发测试之用
查看>>
从一个职校走出来的高级程序员
查看>>
案例:低迷的产品研发团队
查看>>
Hadoop系列之一:大数据存储及处理平台产生的背景
查看>>
vector容器与find算法
查看>>
《从零开始学Swift》学习笔记(Day 19)——函数参数传递
查看>>
corosync+pacemaker高可用集群
查看>>
看完就能出去神侃,来自研发第一线的“区块链”扫盲文(一)
查看>>