析构函数很奇怪。我试图通过使用“智能”参考管理来消除使用一次性模式的需要,确保垃圾收集器可以在正确的时间收集对象。在我的一个析构函数中,我不得不等待来自另一个对象的事件,我注意到它没有。应用程序只是关闭,析构函数在执行过程中被终止。 我希望总是允许析构函数完成运行,但是下面的测试表明这不是真的。
using System;
using System.Diagnostics;
using System.Threading;
namespace DestructorTest
{
class Program
{
static void Main( string[] args )
{
new DestructorTest();
new LoopDestructorTest();
using ( new DisposableTest() ) { }
}
}
class DestructorTest
{
~DestructorTest()
{
// This isn't allowed to finish.
Thread.Sleep( 10000 );
}
}
class LoopDestructorTest
{
~LoopDestructorTest()
{
int cur = 0;
for ( int i = 0; i < int.MaxValue; ++i )
{
cur = i;
}
// This isn't allowed to finish.
Debug.WriteLine( cur );
}
}
class DisposableTest : IDisposable
{
public void Dispose()
{
// This of course, is allowed to finish.
Thread.Sleep( 10000 );
}
}
}
那么,是不是保证完成运行的析构函数?
答案 0 :(得分:16)
那么,是不是保证完成运行的析构函数?
没有。根据我的记忆,当进程终止时,它会给终结器几秒钟的执行时间,然后突然终止进程。你不会想要一个糟糕的终结器来阻止一个进程完成,是吗?
您应该将最终确定视为“尽力而为”的清理 - 特别是,在整个系统突然关闭的情况下,例如BSOD或断电, 不会发生 。
编辑:我发现了一些blog post from Joe Duffy 形式的伪文档如果在停止所有正在运行的线程的过程中锁被孤立,则关闭代码路径将无法获取锁。如果这些采集是在非超时(或长时间超时)获取的情况下完成的,则会发生挂起。为了解决这个问题(以及可能发生的任何其他类型的挂起),CLR会发出一个看门狗线程来监视终结器线程。虽然可配置,但默认情况下CLR会让终结器在变得不耐烦之前运行2秒;如果超过此超时,终结器线程将停止,并且继续关闭而不会耗尽终结器队列的其余部分。
答案 1 :(得分:1)
那么,是不是保证完成运行的析构函数?
虽然它不在您的代码中,但可能存在从Dispose()
方法明确调用GC.SuppressFinalize的情况。这压制了最终化,并且适用于不需要它的对象。
这可以显着提高性能,因为可终结对象将始终存在于一个垃圾收集中,即它将被提升为gen1甚至gen2 GC,其具有与之相关的更高成本。
答案 2 :(得分:1)
.NET没有析构函数。您的代码包含终结器。
当对象被垃圾收集时调用终结器,而不是在它被取消时调用。它们的执行时间也有限,以防止悬挂物体。
答案 3 :(得分:0)
另一种方法是在垃圾收集器释放内存时调用终结器。
但是,如果你的程序最多需要1Mb的内存,但是在一台内存为10Mb的机器上运行,那么grabage collector的有效实现就是什么都不做(因为程序有足够的内存来运行执行)。在这种情况下,永远不会召唤终结者。