CLR内部异常(上)
当我们提到CLR里的“异常”,要注意一个很重要的区别。有通过如C#的try/catch/finally暴露给应用程序,并由运行时提供机制全权实现的托管异常。也有运行时自己使用的异常。大部分运行时开发人员很少需要想到如何实现并暴露托管异常模型。但每个运行时开发人员都应该懂得CLR实现里是怎么使用异常的。为了保持区分,本文将托管程序抛出并捕捉的称为托管异常,而将运行时自己使用的错误处理方式称为 CLR内部异常。本文主要讨论CLR内部异常。
异常在什么地方有用?
异常几乎在所有地方都有用。最有用的地方就是抛出或捕捉异常的函数里,因为需要显式编写代码来抛出异常或者捕捉其并优雅的处理异常。即使一个函数本身不抛出异常,它也有可能调用抛出异常的函数。这样该函数必须在异常抛出的时候行为正常。明智的使用支持物(holders)可以极大简化正确编写这类代码。
为什么CLR内部异常是不同的?
CLR内部异常更像C++异常,但不完全是。CLR可以在Mac OSX、BSD还有Windows下编译。操作系统和编译器的差异使得我们不能仅使用标准C++的try/catch。另外,CLR内部异常还提供了类似托管代码的“finally”和“fault”这样的功能。
通过一些宏,编写异常处理代码就像标准C++那样简单。
捕捉异常
EX_TRY
最基本的宏是:EX_TRY / EX_CATCH / EX_END_CATCH,使用方法如下:
EX_TRY
// 调用一些函数,也许会抛出一个异常
Bar();
EX_CATCH
// 在这里,那就有错误发生了
m_finalDisposition = terminallyHopeless;
EX_END_CATCH(RethrowTransientExceptions)
EX_TRY宏就是引入try块,很像C++的“try”,除了其还添加了一个大括号:“{”。
EX_CATCH
EX_CATCH宏结束一个try块,并添加一个大括号:“}”,并且开始catch块。跟EX_TRY类似,其也添加了一个大括号来开始catch块。
这里和C++异常有很大的不同:CLR开发者根本不明确捕捉什么。实际上,这些宏捕捉包括类似AV的非C++异常或托管异常的任何东西。如果一块代码只需要捕捉一个或者一小部分异常,那么它需要捕捉并检查异常,然后将所有不相关的异常再次抛出。
需要再次指明的是EX_CATCH宏捕捉任何东西。这个可能不是一个函数需要的。下两个章节讨论如何处理不应该被捕捉的异常。
GET_EXCEPTION() & GET_THROWABLE()
当一个CLR开发人员捕捉到一个东西,那么他要如何决定做什么?取决于需求,有几个选项:
第一,无论捕捉到什么(C++)异常,都是继承自全局的Exception类的类的实例。一些继承类很明显,如OutOfMemoryException。另一些则有些领域相关,如EETypeLoadException。还有些类只是系统异常的简单封装,如CLRException(包含OBJECTHANDLE字段指向一个托管异常),或HRException(HRESULT的封装)。如果最初的异常不是从Exception继承来的,那么宏会给其做一个封装。(注意所有异常都是系统自带而且众所周知的)。
第二,每个CLR内部异常都有一个关联的HRESULT值。有时像HRException那样,值从某个COM对象来的,但内部异常和Win32 api错误值也有HRESULT值。
最后,几乎所有CLR内部发生的异常都有可能传递到托管代码那边,CLR内部异常都有跟其对应的托管异常。创建托管异常不是必须的,但是总有办法获取它。
那么,CLR开发人员将如何给一个异常分类呢?
常用的做法是,通过异常关联的HRESULT值分类,而且有一个很简单的办法取值:
HRESULT hr = GET_EXCEPTION()->GetHR();
通过对应的托管异常对象获取更多信息是更便捷的办法。如果异常要传递到托管代码,无论是即时还是被捕捉稍后处理,都是需要这个托管对象的。而且这个异常对象也很容易读取,其是一个托管的objectref引用,因此可以用常规办法:
OBJECTREF throwable = NULL;
GCPROTECT_BEGIN(throwable);
// . . .
EX_TRY
// . . . do something that might throw
EX_CATCH
throwable = GET_THROWABLE();
EX_END_CATCH(RethrowTransientExceptions)
// . . . do something with throwable
GCPROTECT_END()
有时,虽然是异常实现的底层,无法避免要用到C++异常对象。如果C++异常的类型很重要,也有一些轻量级的RTTI函数来帮助归类异常,如:
Exception *pEx = GET_EXCEPTION();
if (pEx->IsType(CLRException::GetType())) {/* ... */}
可以反馈一个异常是否是(或继承自)CLRException。
EX_END_CATCH(RethrowTransientExceptions)
在上面的例子中,“RethrowTransientExceptions”是宏EX_END_CATCH的一个参数;它是三个预定义的宏,并可以看成“异常的性格”。下面是这些宏的解释:
- SwallowAllExceptions: 命名很简单巧妙。如名字所示,它吞没任何对象。显而易见,通常不是正确的做法。
- RethrowTerminalExceptions: 一个更好的名字应该是"RethrowThreadAbort", 也就是这个宏的作用。
- RethrowTransientExceptions:"临时"异常的最好定义是,如果重试则该异常在其它环境里有可能不再发生。下面这些是临时异常:
- COR_E_THREADABORTED
- COR_E_THREADINTERRUPTED
- COR_E_THREADSTOP
- COR_E_APPDOMAINUNLOADED
- E_OUTOFMEMORY
- HRESULT_FROM_WIN32(ERROR_COMMITMENT_LIMIT)
- HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY)
- (HRESULT)STATUS_NO_MEMORY
- COR_E_STACKOVERFLOW
- MSEE_E_ASSEMBLYLOADINPROGRESS
CLR开发人员在不确定的情况下一般应该使用RethrowTransientExceptions.
但在任何情况下,编写EX_END_CATCH的开发人员都需要考虑捕捉哪些异常,并只捕捉这些异常。而且,因为这个宏捕捉所有的东西,不去捕捉一个异常的唯一方法就是重新抛出它。
如果一个EX_CATCH / EX_END_CATCH块正确分类异常,并在必要的时候重新抛出,那么SwallowAllExceptions就是告诉宏不必重新抛出异常的办法。
EX_CATCH_HRESULT
有的时候需要的就是异常对应的那个HRESULT值,特别是针对COM的代码。对于这些情况,使用EX_CATCH_HRESULT宏比编写一个EX_CATCH块简单的多。一个典型代码片段如下:
HRESULT hr;
EX_TRY
// code
EX_CATCH_HRESULT (hr) return hr;
然而,虽然很诱人,但不总是正确的。EX_CATCH_HRESULT捕捉所有的异常,保存HRESULT,并丢掉原始异常。因此,除非丢掉异常这个行为是函数所需要的,否则EX_CATCH_HRESULT并不是很合适。
EX_RETHROW
如上所述,异常宏捕捉所有异常;捕捉一个指定异常的唯一办法是先捕捉所有的异常,再将除了要捕捉的其它异常再次抛出。因此,当一个异常被捕捉,处理之后,结果其不是要被捕捉的,那它可能会被重新抛出。EX_RETHROW宏就是用来抛出相同异常的。
作者:懿民
链接:https://www.jianshu.com/p/9b3d348c292d
来源:简书。
CLR内部异常(上)的更多相关文章
- CLR内部异常(下)
直接使用SEH 有些情况里直接使用SEH会更合适一些.特别是,如果需要在第一次遍历(first pass - SEH异常处理流程里的第一遍处理)时需要执行某些操作时,也就是在堆栈向上展开之前,SEH是 ...
- CLR内部异常(中)
不捕捉某一个异常 常常有这种情况,代码不需要捕捉异常,但需要执行一些清理或者修正操作.虽然不总是,支持物(holders)经常用在这种场景里.在支持物(holders)不适用的情况里,CLR提供了两个 ...
- EF并发问题,在提供程序连接上启动事务时出错。有关详细信息,请参阅内部异常。
1 Entities data=new Entities(); var list = from p in data.Record where p.CreateTime >= d &&am ...
- 【性能诊断】六、并发场景的性能分析(windbg案例,大量的内部异常造成CPU飙升)
在做产品的某个核心模块的性能优化时,发现压到100并发时应用服务器的CPU就飙升至90%以上,50并发以后TPS就基本定格在一个数值上.使用性能监视器收集应用服务器的数据,发现每秒的.NET CLR ...
- 查看.NET应用程序中的异常(上)
内存转储是查明托管.NET应用程序中异常的原因的一种极好的方法,特别是在生产应用程序中发生异常时.当您在无法使用Visual Studio的应用程序中跟踪异常时,cdb和sos.dll的使用技术就变成 ...
- ORACL内部异常:
ORACL内部异常: ORA-00001: 违反唯一约束条件 (.) ORA-00017: 请求会话以设置跟踪事件 ORA-00018: 超出最大会话数 ORA-00019: 超出最大会话许可数 OR ...
- TP5内部异常API数据输出的自定义方法编写
需求:利用postman进行请求api接口过程中 关于一些数据输出异常的情况下 我们希望通过自己编写一些类和方法 实现便于后端人员进行根据提示进行调试处理! 以下测试的时候 请设置 app_debug ...
- 聊聊“装箱”在CLR内部的实现
原文连接:https://mattwarren.org/2017/08/02/A-look-at-the-internals-of-boxing-in-the-CLR/ 作者 Matt Warren. ...
- Dynamics AX 2012 R2 AIF 内部异常
今天,Reinhard发现某个入站端口,突然一直报错: The server was unable to process the request due to an internal erro ...
随机推荐
- -透明度中百分比与十六进制的对应关系 MD
目录 目录 透明度中百分比与十六进制的对应关系 计算代码 对应关系表 Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao ...
- Sqlserver 总结(2) 存储过程
目录 写在前面 什么是存储过程 存储过程的优点 存储过程的分类 1.只返回单一记录集的存储过程 2.没有输入输出的存储过程 3.有返回值的存储过程 4.有输入参数和输出参数的存储过程 5.同时具有返回 ...
- SpringDataRedis
一.简介 1.SpringData和Redis Redis将数据存储到内存的,速度快.可以解决请求mysql数据库过多而导致mysql崩溃的问题. SpringData是专门用来控制Redis的工具, ...
- 为什么UDP有时比TCP更有优势?
随着网络技术飞速发展,网速已不再是传输的瓶颈,UDP协议以其简单.传输快的优势,在越来越多场景下取代了TCP.1.网速的提升给UDP稳定性提供可靠网络保障 CDN服务商Akamai(NASDAQ: A ...
- Spring源码——IOC控制反转
1.基础知识 Spring有两个核心功能,分别是ioc和aop,其中ioc是控制反转,aop是切面编程. 在ioc中,还有一个名次叫DI,也就是依赖注入.嗯,好像IOC和DI是指同一个,好像又感觉他俩 ...
- 一 python并发编程之多进程
一 进程与程序 二 并发与并行 三 同步\异步和阻塞\非阻塞 四 进程的创建 五 进程的终止 六 进程的层次结构 七 进程的状态 八 进程并发的实现 一 进程与程序 什么是进程: 进程的概念:我们知道 ...
- python pip安装解决方法
一招解决python pip install 安装库失败 PIP是python强大的安装利器,但是我们经常遇到安装库失败的问题,以下本人觉得最有效的解决方法: 1.打开 https://www.l ...
- C# NPOI 导入与导出Excel文档 兼容xlsx, xls(xf13中已经引用了xlsx的npoi)
这里使用的NPOI版本为: 2.1.3.1 官方下载地址: http://npoi.codeplex.com/releases 版本内包含.Net 2.0 与.Net 4.0 .Net 4.0中包含文 ...
- caement Archaic spelling of cement
caement Archaic spelling of cement. caement Alternative forms[edit] caement (archaic) cæment (archai ...
- IDEA修改选取单词颜色和搜索结果的颜色
一.修改选取单词颜色 下图所示,选取Father后背景为淡蓝色,其它相同单词背景为灰色,根本看不清楚 修改配置 1.修改选取文本背景色为78C9FF 2.修改相同文本背景色为78C9FF,包括iden ...