关键字:Exception StackTrace
1.问题描述
1.现象和代码
1 | /* |
运行结果:
2.现象描述和分析
上述代码中,抛出异常的地方为MethodC,但是并没有在出现异常的地方进行try-catch异常捕获。
而是在调用代码的外层进行了异常捕获try-catch
结果,在捕获异常打印的log中,显示的message信息中,并没有定位到真正出错的那一行,而是只定位到了打印log的那一行。
为什么没有打印出错那一行的信息?分析原因:
当代码抛出现异常的时候,异常会一层一层向上抛,也就是找调用的地方有没有try-catch进行异常的捕获。如果没有人工进行异常的捕获,那么,最终系统会接收这个异常并且将此异常抛出。Unity引擎的做法是打印抛出异常的堆栈信息。
在上述代码中,由于MethodB有对异常的捕获,因此,异常不再向上抛出,而是被MethodB处的try-catch捕获。
而我们在捕获异常的地方打印了异常信息,异常信息里面并没有包含真正出错的位置,只有将异常信息和打印异常信息的堆栈.
3.打印异常堆栈跟踪信息
正确的打印异常的堆栈信息如下:1
2
3
4
5
6
7
8try
{
MethodB();
}
catch (System.Exception e)
{
Debug.LogError("MethodA exception:"+ e.Message+"\n\n"+e.StackTrace);
}
其中,exception.StackTrace中包含了真正的异常抛出的堆栈信息。
运行截图:
2.关于异常捕获
异常派生类的命名规范一般为:类型动作Exception。
try-catch-finally
多个catch块,捕获不同的异常。具体的放在前面,通用的放在后面。
catch也可以捕获任何异常,但是不收集异常的内容:
1
2
3
4
5
6
7
8try
{
MethodB();
}
catch // 捕获异常,但是不获取异常消息
{
// throw; // 再抛出同样的异常
}
catch末尾:
- 重新抛出相同异常,向调用栈高一层代码通知该异常的发送. throw;
- 抛出一个不同异常,向调用栈高一层代码发送其他异常信息. throw new xxException();
- 让调用的线程从catch块的底部退出。贯穿到catch块的底部
堆栈跟踪(stack trace):对方法调用的跟踪。是栈。虽然翻译的是堆栈。
向AppDomain的FirstChanceException事件登记。这样,只要AppDomain中发生异常就会收到通知。这个通知是在开始搜索任何catch块之前发生的。
- 事件:
- System.AppDomain.CurrentDomain.FirstChanceException (.Net Core 2.1,2.0;.Net Framework 4.0以上)
- System.AppDomain.CurrentDomain.UnhandledException
CLR中: 非System.Exception的派生类的异常对象,被包装为System.Runtime.CompilerServices.RuntimeWrappedException的对象
属性名称 | 访问 | 类型 | 说明 |
---|---|---|---|
Message | readonly | String | 包含辅助性文字说明,指出抛出异常的原因。如果抛出的异常未处理,该消息通常被写入日志。 |
Data | readonly | IDictionary | 引用一个键值对集合。通常,代码在抛出异常前在该集合中添加记录项;捕捉异常的代码可在异常恢复过程中查询记录项并利用其中的信息 |
Source | read/write | String | 包含生成异常的程序集的名称 |
StackTrace | readonly | String | 包含抛出异常之前调用过的所有方法的名称和签名。即:包含出错位置的调用堆栈信息 |
TargetSite | readonly | MethodBase | 包含抛出异常的方法 |
HelpLink | readonly | String | 包含异常的文档的URL。 |
InnerException | readonly | Exception | 如果当前异常是在处理一个异常时抛出的,该属性就指出上一个异常时什么。通常为null。Exception类型提供了GetBaseException来遍历内层异常构成的链表,并返回最初抛出的异常.(至少会得到最初抛出的异常) |
HResult | read/write | Int32 | 跨越托管和本机代码边界时使用的一个32位值。 |
System.Exception.StackTrace属性:
- 实际是实时访问了CLR中的代码,并且返回一个调用堆栈跟踪的字符串。
异常抛出时,CLR在内部记录throw指令的位置(抛出位置)。一个catch块捕捉到该异常时,CLR记录捕捉的位置。在catch块内访问被抛出的异常对象StackTrace属性,负责实现该属性的代码会调用CLR内部的代码,返回一个字符串来指出从异常抛出位置到异常捕捉位置的所有方法。
抛出异常时,CLR会重置异常起点。也就是说,CLR只记录最新的异常对象的抛出位置。(如果同一个exception,catch之后不做处理,再将其抛出,由于是同一个异常的缘故,所以还是可以获得StackTrace信息的) - 构造Exception的派生类型时,StackTrace的初值是null。
- StackTrace属性返回的字符串不包含调用栈中比异常发生出更早的方法。即只包含到了异常发生的地方。
- 要获得从线程起始处得到异常处理程序之间的完整堆栈跟踪,需要使用System.Diagnostics.StackTrace类型。
3.关于堆栈跟踪
1.堆栈跟踪知识点
要获得从线程起始处得到异常处理程序之间的完整堆栈跟踪,需要使用System.Diagnostics.StackTrace类型。
该类型定义了一些属性和方法,允许开发人员程序化处理堆栈跟踪以及构成堆栈跟踪的栈帧(stackFrame)
StackFrame:代表当前线程的调用栈中的一个方法调用。执行线程的过程中,每个方法调用都会在调用栈中创建并压入一个StackFrame.
构造StackTrace对象:可以从线程起始处到StackTrace对象的构造位置构造栈帧。也可以作为参数传给Exception派生对象来初始化栈帧。
程序集的调试符号存储在.pdb文件中。如果CLR能找到程序集的调试符号文件,调用堆栈信息中就会包括源代码文件路径和代码行号。
调用栈记录的是线程的返回位置,而不是来源位置,或者因此JIT在编译期间进行了优化,将一些方法内联,会导致StackTrace调用的堆栈信息中有的调用方法不在调用信息中。
System.Environment.StackTrace
使用StackTrace获取调用堆栈信息
- namespace:System.Diagnostics.StackTrace
- 构造方法:主要有三种
- 功能说明:初始化当前调用线程的当前帧调用堆栈的实例。
- bool: 是否收集source信息:true-> 收集文件名、line number、列号
- Int32: 跳过指定调用帧(stackFrame)(跳过几个函数调用)
- Exception: 创建的StackTrace描述的是传入的异常发生时候的信息。
- 其他主要方法:
- FrameCount: 当前stack trace的函数调用帧数
- GetFrame: 返回指定帧的StackFrame
- GetFrames: 返回StackFame数组的copy
StackFrame提供了当前线程函数调用的栈信息
- 线程执行时,每一次的函数调用,都会生成一个StackFrame,并且压栈。
- StackFrame总是包含MethodBase信息。file name, line number, column number信息是可以选的。
- 有调试符号表的Debug版本中可以获取到详细信息,而一般Release版中没有。
- 构造StackFrame的时候,可以选择是否收集source信息、跳过指定帧数,或者直接指定文件的行号、列号等。(在获取堆栈调用信息的时候,不用构造实例)
- 主要方法:
- GetFileName
- GetFileLineNumber
- GetFileColumnNumber
- GetMethod
2.调用堆栈信息代码示例
1 | /* |
调用GetStacksInfo()的运行截图:
调用GetFormedStacksInfo()的运行截图:
调用Get1FormedStackInfo()的运行截图:
3.创建Exception的StackTrace
1 | private void MethodA() |
最后,打印的log信息正确的获取到了报异常的位置:
异常代码位置:
运行截图:
4.总结
- Exception的捕获,以及Exception结构中字段的使用(Message/StackTrace)
- catch捕获异常后的处理。(throw or not throw)
- 每一次的函数调用,都会生成一个StackFrame,并且压栈(可以使用StackTrace的资源来源)
- StackTrace和StackFrame的使用
5.学习过程
- 使用exception.StackTrace获得异常的堆栈信息的知识点
- 为了把知识点串起来,阅读《CLR via C#》的Exception章节
- 对于如何获取堆栈信息感到好奇,阅读.NET FRAMEWORK StackTrace相关文档(https://docs.microsoft.com/zh-cn/dotnet/api/system.diagnostics.stacktrace?view=netframework-4.7.2)