m0s*_*0sa 1 .net c# dll idisposable finalizer
我有2个程序集,A包含Main方法和Foo类,它使用Bar来自程序集B的类:
杆组件(组件B):
public sealed class Bar : IDisposable {
/* ... */
public void Dispose() { /* ... */ }
}
Run Code Online (Sandbox Code Playgroud)
Foo类(程序集A):
public class Foo : IDisposable {
private readonly Bar external;
private bool disposed;
public Foo()
{
Console.WriteLine("Foo");
external = new Bar();
}
~Foo()
{
Console.WriteLine("~Foo");
this.Dispose(false);
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed) return;
if (disposing) external.Dispose();
disposed = true;
}
}
Run Code Online (Sandbox Code Playgroud)
入口点(在程序集A中):
class Program
{
static void Main(string[] args)
{
try
{
var foo = new Foo();
Console.WriteLine(foo);
}
catch (FileNotFoundException ex)
{
// handle exception
Console.WriteLine(ex.ToString());
}
Console.ReadLine();
}
}
Run Code Online (Sandbox Code Playgroud)
这个软件的一个要求是它必须在缺少dll时优雅地处理这个案例.
因此,当我删除程序集B并启动应用程序时,我希望main方法中的try catch块处理当程序集B丢失时抛出的FileNotFoundException.它有点像,但这就是问题的起源......
当应用程序继续(在控制台中输入一行)时,会调用Foo类的终结器(?!),尽管没有创建Foo实例 - 尚未调用构造函数.由于没有该类的实例,因此无法在外部调用实例上的GC.SupressFinalize.在没有B程序集的情况下运行项目时,您在控制台输出中看到的唯一内容是~Foo.
所以问题:
一些背景:我在编写插件启用企业应用程序时遇到了这个问题,如果插件部署文件夹中缺少dll并标记有故障的插件,则必须继续操作.我认为围绕外部插件加载过程的try-catch块就足够了,但显然它没有,因为在捕获第一个异常之后仍然会调用终结器(在GC线程上),最终崩溃了应用程序.
备注上面的代码是我可以写的最简约的代码,用于在终结器中重现异常.
备注2如果我在Foo构造函数中设置断点(在删除Bar的dll之后),则不会命中它.这意味着如果我在构造函数中设置一个创建关键资源的语句(在新建Bar之前),它将不会被执行,因此不需要调用终结器:
// in class Foo
public Foo() {
// ...
other = new OtherResource(); // this is not called when Bar's dll is missing
external = new Bar(); // runtime throws before entering the constructor
}
protected virtual void Dispose(bool disposing) {
// ...
other.Dispose(); // doesn't get called either, since I am
external.Dispose(); // invoking a method on external
// ...
}
Run Code Online (Sandbox Code Playgroud)
备注3 一个明显的解决方案是实现IDisposable,如下所示,但这意味着打破参考模式实现(即使FxCop会抱怨).
public abstract class DisposableBase : IDisposable {
private readonly bool constructed;
protected DisposableBase() {
constructed = true;
}
~DisposableBase() {
if(!constructed) return;
this.Dispose(false);
}
/* ... */
}
Run Code Online (Sandbox Code Playgroud)
Eri*_*ert 10
为什么即使没有创建类的实例,也会调用终结器?
这个问题毫无意义.显然是创建了一个实例; 如果没有创建实例,终结器将最终完成什么?您是否试图告诉我们该终结器中没有"此"参考?
尚未调用构造函数
无法调用构造函数,因为jitting构造函数引用了缺少类型的字段.怎样才能调用甚至不能被jitted的构造函数体?
您似乎认为仅仅因为无法调用构造函数,无法创建实例.这根本不符合逻辑.显然,在调用ctor 之前必须有一个实例,因为对该实例的引用作为"this"传递给它.因此内存管理器创建一个实例 - 垃圾收集器知道已分配内存 - 然后它调用构造函数.如果调用构造函数抛出异常 - 或者由异步异常(例如线程中止)中断 - 那里仍然存在垃圾收集器已知的实例,因此在死亡时需要完成.
由于该对象永远不会被分配给任何实时变量 - 它不可能,因为赋值发生在ctor之后,并且当抖动试图使它jit时ctor抛出 - 它将被确定为在下一代零时死亡采集.然后将它放到终结器队列中,这将使其保持活动状态.
仍然会调用终结器(在GC线程上),最终崩溃应用程序.
然后修复终结器,使其不这样做.
请记住,ctor可以随时被异步异常中断,例如线程中止.您不能依赖于终结器中维护的对象的任何不变量.终结器是非常奇怪的代码; 您应该假设它们可以在任意线程上以任意顺序运行,并且对象处于任意不良状态.您需要在终结器中编写极其防御性的代码.
如果我在Foo构造函数中设置断点(在删除Bar的dll之后),则不会命中它.
正确.正如我所说,构造函数体甚至不能被jitted.你怎么能在一个甚至无法进行jitted的方法中找到一个断点?
这意味着如果我在构造函数中设置一个创建关键资源的语句(在新建Bar之前),它将不会被执行,因此不需要调用终结器.
无论你想终结需要被称为是完全不相干的垃圾收集器.终结器可能具有其他语义,而不仅仅是清理资源.垃圾收集器不会尝试在心理上确定开发人员的意图,并决定是否需要调用终结器. 对象被分配并有一个终结器,你没有压制它的最终确定,所以它将被最终确定.如果您不喜欢,那么不要制作终结者.你做了一个终结器,因为大概你想要完成对象的所有实例,所以它们就是这样.
坦率地说,我会重温你的基本情景.您可以安全地恢复并继续在appdomain中执行代码的想法,这对我来说似乎是一个非常糟糕的主意.实现这一目标将非常困难.