我可以在终结器方法中使用哪些对象?

Iva*_*lov 13 .net c#

我有一个类应该在处理或最终时删除一些文件.在终结器中我不能使用其他对象,因为它们可能已经被垃圾收集了.

我是否遗漏了关于终结器的一些观点并且可以使用弦乐?

UPD:类似的东西:

public class TempFileStream : FileStream
{
    private string _filename;

    public TempFileStream(string filename)
        :base(filename, FileMode.Open, FileAccess.Read, FileShare.Read)
    {
        _filename = filename;
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        if (_filename == null) return;

        try
        {
            File.Delete(_filename); // <-- oops! _filename could be gc-ed already
            _filename = null;
        }
        catch (Exception e)
        {
            ...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

ang*_*son 22

是的,您肯定可以在终结器和许多其他对象类型中使用字符串.

对于所有这些的权威来源,我会选择通过C#撰写的CLR,第3版,由Jeffrey Richter编写.在第21章中,对此进行了详细描述.

无论如何,这是真正发生的事情......

在垃圾收集期间,任何具有仍希望被调用的终结器的对象都被放置在一个称为可释放列表的特殊列表中.

此列表被视为根,就像静态变量和实时局部变量一样.因此,任何物体对象是指,等等递归从垃圾回收周期这段时间删除.它们将在当前的垃圾收集周期中存活下来,就好像它们没有资格开始收集一样.

请注意,这包括字符串,这是您的问题,但它也涉及所有其他对象类型

然后,在稍后的某个时间点,终结器线程从该列表中拾取对象,并在这些对象上运行终结器,然后从该列表中取出这些对象.

然后,下次运行垃圾收集时,它会再次找到相同的对象,但这次终结器不再需要运行,它已经被执行,因此正常收集对象.

在我告诉你什么不起作用之前,让我举一个例子来说明.

假设您有对象A到Z,并且每个对象引用下一个对象,因此您有对象A引用对象B,B引用C,C引用D,依此类推,直到Z.

其中一些对象实现了终结器,它们都实现了IDisposable.让我们假设A没有实现终结器而B实现,然后其他一些也是如此,对于超出A和B的这个例子来说,这并不重要.

您的程序保留对A的引用,并且只保留A.

在一个普通的,正确的使用模式中,你将处置A,它将处理B,它将处理C等,但你有一个bug,所以这不会发生.在某些时候,所有这些对象都有资格收集.

此时GC会找到所有这些对象,但是后来注意到B有一个终结器,它还没有运行.因此,GC将B放在可释放列表上,并递归地将C,D,E等从GC列表中取出到Z,因为由于B突然变得不合格收集,所以其余部分也是如此.请注意,其中一些对象也放置在可自由列表本身,因为它们自己有终结器,但它们引用的所有对象都将在GC中存活.

然而,收集了A.

让我清楚地说明上一段.此时,已经收集了A,但是直到Z的B,C,D等仍然活着,好像什么也没发生过一样.虽然您的代码不再具有对其中任何一个的引用,但是可释放列表具有.

然后,终结器线程运行,并最终确定可释放列表中的所有对象,并列表中取出对象.

下次运行GC时,现在会收集这些对象.

所以这当然有效,那么最重要的是什么呢?

问题在于终结器线程.该线程不假设它应该最终确定这些对象的顺序.它没有这样做,因为在许多情况下它不可能这样做.

正如我上面所说,在普通的世界中,你会在A上调用dispose,它处理B,它处理C等.如果其中一个对象是一个流,引用该流的对象在调用Dispose时可能会说"在处理流之前,我会继续冲洗我的缓冲区." 这是完全合法的,许多现有代码都是这样做的.

但是,在终结线程中,不再使用此顺序,因此如果在引用它的对象之前将流放置在列表上,则在引用它的对象之前完成流,从而关闭流.

换句话说,你不能做的事情总结如下:

您无法访问对象引用的任何对象,它们具有终结器,因为您无法保证在终结器运行时这些对象将处于可用状态.该物体仍然会出现,在内存中,并没有收集,但他们可能已经关闭,终止,最终确定等.

那么,回到你的问题:

问:我可以在终结器方法中使用字符串吗?
答:是的,因为字符串没有实现终结器,并且不依赖于具有终结器的其他对象,因此在终结器运行时会活着并且踢.

使你采取错误路径的假设是qustion的第二句话:

在终结器中我不能使用其他对象,因为它们可能已经被垃圾收集了.

正确的句子是:

在终结器内部,我不能使用具有终结器的其他对象,因为它们可能已经完成.


对于某个例子,终结器无法知道正确完成两个对象的顺序,请考虑两个相互引用的对象,并且两个对象都有终结器.终结器线程必须分析代码以确定它们通常将被处理的顺序,这可能是两个对象之间的"舞蹈".终结器线程不执行此操作,它只是在一个接一个之前完成,并且您无法保证哪个是第一个.


那么,有没有时间从我自己的终结器访问具有终结器的对象是否安全

唯一有保障的安全方案是您的程序/类库/源代码拥有这两个对象,以便您知道它是.

在我解释之前,这不是很好的编程实践,所以你可能不应该这样做.

例:

您有一个Cache将数据写入文件的对象,该文件永远不会保持打开状态,因此仅在对象需要向其写入数据时才会打开.

你有另一个对象,CacheManager它使用第一个对象,并调用第一个对象给它数据写入文件.

CacheManager有一个终结者.这里的语义是,如果管理器类被收集但没有处理,它应该删除缓存,因为它不能保证它们的状态.

但是,可以从缓存对象的属性中检索缓存对象的文件名.

所以问题是,我是否需要将该文件名的副本复制到manager对象中,以避免在最终确定期间出现问题?

不,你没有.当管理器完成时,缓存对象仍然在内存中,它引用的文件名字符串也是如此.但是,您无法保证缓存对象上的任何终结器尚未运行.

但是,在这种情况下,如果您知道缓存对象的终结器不存在,或者未触及该文件,则管理器可以读取缓存对象的filename属性,并删除该文件.

但是,既然你现在有一个非常奇怪的依赖,我肯定会反对它.