例外规则
类型构造函数的另一个独特行为是运行库管理类型构造函数中的异常的方式。在引发异常时,运行库将开始查找其筛选器指定它可以处理该异常的最近的 catch 子句。现在,以图 4 中的 C# 控制台模式程序为例。
该程序将产生如图 5 所示的结果。有两个行为需要注意。第一,该程序不会为类型是 ApplicationException 的异常寻找 catch 子句,即使这种类型的异常是从静态构造函数引发的也如此。运行库将停止任何尝试保留类型构造函数的异常,并将该异常包装到一个新的 TypeInitializationException 对象内。然后,该类型构造函数中引发的原始异常会位于 TypeInitializationException 对象的 InnerException 属性中。
可能发生这种特殊行为的一个原因是,第二次尝试访问该类型的静态属性时,运行库不再尝试调用该类型构造函数,而是引发在第一个迭代中观察到的同一个异常。运行库不会给类型构造函数第二次机会。如果异常是从静态字段初始值设定项引发的,也存在同样的规则。正如您在前面看到的那样,静态字段初始值设定项的代码在隐式类型构造函数的内部执行。
TypeInitializationException 在应用程序中可能是一个致命错误,因为它会生成类型无用。如果有可能从错误中恢复,您应该计划捕获类型构造函数中的任何异常,如果错误无法调和,则应该允许应用程序终止。
返回页首
构造函数锁
类型构造函数还有一个奇怪的行为。这种行为是由公共语言架构(Common Language Infrastructure,CLI)规范导致的,该规范确保一个类型构造函数只执行一次,除非用户代码进行显式调用。要在多线程环境中强制此保证,需要一个锁来同步线程。运行库线程必须在调用类型构造函数之前获得此锁。
采用锁的程序必须非常小心,以免产生死锁的情况。要了解它是如何发生的,请尝试使用图 6 中不好的代码将运行库置于死锁状态。此代码会将线程 A 发送到 Static1 的类型构造函数中,将线程 B 发送到 Static2 的类型构造函数中,然后将两个线程都置于睡眠状态。线程 A 将唤醒,并需要访问 Static2,而 B 已经将其锁定。线程 B 将唤醒,并需要访问 Static1,而 A 已经将其锁定。线程 A 需要线程 B 持有的锁,而线程 B 需要线程 A 持有的锁。这就是一个典型的死锁情况。
图 6 中的应用程序不会导致死锁,而是会生成图 7 所示的结果。它证明了该 CLI 规范还同时保证了运行库不允许类型构造函数产生死锁的情形,除非用户代码显式采用了附加锁。
文章来源于领测软件测试网 https://www.ltesting.net/