原文 : Stacktrace improvements in .NET Core 2.1

作者 : Ben Adams

译者 : 张很水

. 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 exception
yield return i;
}
}
}

[搬运] .NET Core 2.1中改进的堆栈信息的更多相关文章

  1. delphi中获取调用堆栈信息

    异常堆栈有利于分析程序的错误,Delphi的Exception有StackTrace属性,但是值为空,因为StackTrace的信息收集Delphi委托给了第三方组件来完成,真是脑子有毛病! 借助于m ...

  2. 【译】.NET Core 3.0 中的新变化

    .NET Core 3.0 是 .NET Core 平台的下一主要版本.本文回顾了 .Net Core 发展历史,并展示了它是如何从基本支持 Web 和数据工作负载的版本 1,发展成为能够运行 Web ...

  3. .NET Core 2.1中的分层编译(预览)

    如果您是.NET性能的粉丝,最近有很多好消息,例如.NET Core 2.1中的性能改进和宣布.NET Core 2.1,但我们还有更多的好消息.分层编译是一项重要的新特性功能,我们可以作为预览供任何 ...

  4. 你所不知道的库存超限做法 服务器一般达到多少qps比较好[转] JAVA格物致知基础篇:你所不知道的返回码 深入了解EntityFramework Core 2.1延迟加载(Lazy Loading) EntityFramework 6.x和EntityFramework Core关系映射中导航属性必须是public? 藏在正则表达式里的陷阱 两道面试题,带你解析Java类加载机制

    你所不知道的库存超限做法 在互联网企业中,限购的做法,多种多样,有的别出心裁,有的因循守旧,但是种种做法皆想达到的目的,无外乎几种,商品卖的完,系统抗的住,库存不超限.虽然短短数语,却有着说不完,道不 ...

  5. [转]【译】.NET Core 3.0 中的新变化

    .NET Core 3.0 是 .NET Core 平台的下一主要版本.本文回顾了 .Net Core 发展历史,并展示了它是如何从基本支持 Web 和数据工作负载的版本 1,发展成为能够运行 Web ...

  6. ASP.NET Core HTTP 管道中的那些事儿

    前言 马上2016年就要过去了,时间可是真快啊. 上次写完 Identity 系列之后,反响还不错,所以本来打算写一个 ASP.NET Core 中间件系列的,但是中间遇到了很多事情.首先是 NPOI ...

  7. 在.NET Core控制台程序中使用依赖注入

    之前都是在ASP.NET Core中使用依赖注入(Dependency Injection),昨天遇到一个场景需要在.NET Core控制台程序中使用依赖注入,由于对.NET Core中的依赖注入机制 ...

  8. .NET跨平台之旅:ASP.NET Core从传统ASP.NET的Cookie中读取用户登录信息

    在解决了asp.net core中访问memcached缓存的问题后,我们开始大踏步地向.net core进军——将更多站点向asp.net core迁移,在迁移涉及获取用户登录信息的站点时,我们遇到 ...

  9. ASP.NET Core 1.0 中的依赖项管理

    var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...

随机推荐

  1. 使用fruitstrap实现命令行将IPA包安装到iOS设备上

    Requirements Mac OS X. Tested on Snow Leopard only. You need to have a valid iPhone development cert ...

  2. SQLAlchemy基础操作一

    用前安装 pip3 install sqlalchemy ORM ORM就是运用面向对象的知识,将数据库中的每个表对应一个类,将数据库表中的记录对应一个类的对象.将复杂的sql语句转换成类和对象的操作 ...

  3. OS.path部分函数的介绍

    OS.path模块中的部分函数的介绍 os.path.abspath(path) #返回绝对路径 os.path.basename(path) #返回文件名 os.path.commonprefix( ...

  4. from Require.js to Webpack(why)

    写在前面: 本文主要参考 From Require.js to Webpack - Part 1 (the reasons),原文作者将项目从 require.js 转移到了 webpack 并详细说 ...

  5. 【ASP.NET Core分布式项目实战】(二)oauth2 + oidc 实现 server部分

    本博客根据http://video.jessetalk.cn/my/course/5视频整理(内容可能会有部分,推荐看源视频学习) 资料 我们基于之前的MvcCookieAuthSample来做开发 ...

  6. 浅析nodeJS中的Crypto模块,包括hash算法,HMAC算法,加密算法知识,SSL协议

    node.js的crypto在0.8版本,这个模块的主要功能是加密解密. node利用 OpenSSL库(https://www.openssl.org/source/)来实现它的加密技术, 这是因为 ...

  7. 接口测试——Java + TestNG 国家气象局接口(json解析)实例

    后端测试,主要以测试接口为主.需要代码支撑,近期便找了个天气接口捣鼓了. 使用到的工具是:Eclipse + TestNG + Maven + ReportNG,全国城市编码:http://www.c ...

  8. cs231n spring 2017 lecture11 Detection and Segmentation 听课笔记

    1. Semantic Segmentation 把每个像素分类到某个语义. 为了减少运算量,会先降采样再升采样.降采样一般用池化层,升采样有各种"Unpooling"." ...

  9. deeplearning.ai 改善深层神经网络 week1 深度学习的实用层面 听课笔记

    1. 应用机器学习是高度依赖迭代尝试的,不要指望一蹴而就,必须不断调参数看结果,根据结果再继续调参数. 2. 数据集分成训练集(training set).验证集(validation/develop ...

  10. hackerrank Ticket

    传送门 题意:n个人排队买票,要把他们拆成k条队到k个窗口买,可以有队伍为空,每条队的顺序保持拆之前的顺序.如果某人和他前一个人买的票相同,就可以打八折,求最小花费. 题解:拆成k条队意味着只有[n- ...