在.NET中抑制流的过早完成

时间:2014-06-18 07:35:49

标签: c# .net garbage-collection

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

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");
}

由于对象ObjectDisposedException的方法Close导致的.net和单声道writer导致代码抛出(非确定性),因为它尝试将缓冲区刷新到stream已被处置。

我理解原因是GCstream之前完成writer。如何更改班级模式以确保stream之前没有处置writer

我厌倦了在构造函数中使用GC.SuppressFinalize(writer),但我不确定它是不是太过于hacky。

修改


我想首先解决终结者的问题。如问题开头所述,该类用作记录器,我想确保在关闭进程之前将writer中的所有行刷新到硬盘上。

3 个答案:

答案 0 :(得分:3)

不要创建终结器,除非您的IDisposable实现真正适用于非托管资源。 FileStreamStreamWriter是受管资源。

此外,自从引入SafeHandle以来,很难想象用例,当任何非托管资源无法包装到托管SafeHandle中时。不要盲目地跟踪MSDN中的IDisposable实现 - 它没关系,但它适用于这种情况,当你的类型同时运行托管和非托管资源时。

删除终结器,然后从GC.SuppressFinalize(this);

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

<强>更新

终结器用于非托管资源清理 您可以将终结器视为关闭文件句柄,网络套接字等的位置。这不适用于任何应用程序逻辑。通常,在最终确定期间,托管对象处于不可用状态 - 无法保证其任何IDisposable(如样本中的streamwriter)尚未最终确定已经

如果您想确定,将该特定日志消息刷新到文件中,而不是写入并调用writer.Flush()。否则,如果您不希望立即进行刷新,请确保在应用程序关闭时您正在为记录器调用dispose。另请注意,您无法防止进程终止,因此不要对您的记录器过于偏执。

答案 1 :(得分:1)

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

在完成流后,重写您的代码,以便处理托管资源。

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

请务必查看Dispose Pattern article on MSDN

答案 2 :(得分:0)

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

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

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

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

-4-有一个Finalize方法尚未运行,但计划在第一次运行时运行,在这种情况下,持有引用的类的终结器不应对它们执行任何操作。

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

有一些罕见的场景涉及弱事件,其中终结器可能能够对托管对象做一些有用的事情,但在几乎所有这些情况下,唯一应该有终结器的类是其唯一目的< / em>用于管理其他与密切相关的对象的最终清理。如果一个人不理解最终确定的所有皱纹,包括短期和长期弱引用之间的差异以及它们如何与终结者进行交互,那么任何试图写入的终结者都可能弊大于利。