抑制.net中流的过早终结

本文关键字:net 抑制 | 更新日期: 2023-09-27 18:09:01

我有以下类似记录器的类模式:

public class DisposableClassWithStream : IDisposable
{
    public DisposableClassWithStream()
    {
        stream = new FileStream("/tmp/file", FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
        writer = new StreamWriter(stream);
    }
    public void WriteLine(string s)
    {
        writer.WriteLine(s);
    }
    ~DisposableClassWithStream()
    {
        Dispose();
    }
    private readonly object disposableLock = new object();
    private bool isDisposed;
    public void Dispose()
    {
        lock (disposableLock)
        {
            if (!isDisposed)
            {
                writer.Close();
                stream.Close();
                GC.SuppressFinalize(this);
                isDisposed = true;
            }
        }
    }
    private FileStream stream;
    private TextWriter writer;
}
和一个非常简单的代码使用这个类:
public static void Main(string[] args)
{
    var t = new DisposableClassWithStream();
    t.WriteLine("Test");
}

代码抛出(不确定性),在。net和mono上,ObjectDisposedException是由对象writer的方法Close引起的,因为它试图将缓冲区刷新到已经被处置的stream

我知道原因是GCwriter之前完成了stream。如何更改类模式以确保stream不会在writer之前被处理?

我想在构造函数中使用GC.SuppressFinalize(writer),但我不确定它是否太粗糙。

编辑:


我想首先解决使用终结器的问题。正如问题开头所提到的,这个类被用作记录器,我想确保在关闭进程之前,writer中的所有行都被刷新到硬盘上。

抑制.net中流的过早终结

不要创建终结器,除非您的IDisposable实现确实适用于非托管资源。

FileStreamStreamWriter为受管资源。

此外,自从SafeHandle被引入以来,很难想象任何非托管资源都不能包装到托管SafeHandle中的用例。不要盲目地遵循MSDN的IDisposable实现-这是可以的,但它是针对这种情况,当您的类型同时操作托管和非托管资源时。

完全删除终结器,并从Dispose中丢弃GC.SuppressFinalize(this);:

public void Dispose()
{
    lock (disposableLock)
    {
        if (!isDisposed)
        {
            writer.Close();
            stream.Close();
            isDisposed = true;
        }
    }
}

终结器用于非托管资源清理。
你可以把终结器看作是关闭文件句柄、网络套接字等的地方。这里不适合任何应用程序逻辑。一般来说,托管对象在结束过程中处于不可用状态——不能保证它的任何IDisposable(如示例中的streamwriter)还没有结束。

如果您想确定,特定的日志消息被刷新到文件中,那么写入它并调用writer.Flush()。否则,如果您不希望立即刷新,请确保在应用程序关闭时为日志记录器调用dispose。还要注意,您不能防止进程终止,所以不要对您的日志记录器过于偏执。

不应该在终结器中清理托管对象。终结器应该只用于清理非托管资源。

按如下方式重写代码,以便在处理完流后处置托管资源。

public static void Main(string[] args)
{
    using (var t = new DisposableClassWithStream())
    {
    t.WriteLine("Test");
    }
}

一定要查看MSDN上的Dispose Pattern文章

当终结器运行时,它保存引用的大多数托管对象将满足以下条件之一:

-1-不关心清理,在这种情况下,终结器不应该对它们做任何操作。

-2-当在终结器清理的上下文中调用时,无法以线程安全的方式执行清理,在这种情况下,终结器不应对它们做任何事情。

-3-已经使用自己的终结器清理了自己,在这种情况下,终结器不应该对它们做任何操作。

-4-有一个Finalize方法,它还没有运行,但计划在第一次有机会运行,在这种情况下,保存引用的类的终结器不应该对它们做任何事情。

不同的对象将满足不同的标准,并且可能很难知道特定对象可能满足哪些标准,但对于绝大多数对象将满足任何标准,所需的处理是相同的:不要在终结器中对它们做任何操作。

有一些罕见的场景涉及到像弱事件这样的事情,其中终结器可能能够对托管对象做一些有用的事情,但在几乎所有这样的情况下,唯一应该具有终结器的类是那些唯一目的是管理其他密切相关对象的终结清理的类。如果不理解终结的所有细节,包括长短弱引用之间的区别,以及它们如何与终结器交互,那么试图编写的任何终结器都可能弊大于利。