C#垃圾收集器似乎太早关闭了我的StreamWriter

时间:2012-07-05 16:53:48

标签: c# .net singleton garbage

我有一个单一的记录器类。在它的析构函数中,我调用Close()打印日志的页脚,然后关闭StreamWriter。

 public void Close()
    {
        WriteLogFileFooter();

        _logFile.Flush();

        _logFile.Close();
    }

问题是当从程序中的其他地方调用System.Enviornment.Exit(1)时(我自己没有编写的部分),页脚永远不会被打印,我的记录器会因尝试写入a而抛出异常封闭的溪流。我只能假设Exit命令导致我的StreamWriter在我的Singleton被破坏之前被关闭。我试图在我的StreamWriter上使用GC.SupressFinalize(),但这似乎没有帮助。

5 个答案:

答案 0 :(得分:12)

您违反了终结器的一条明确规则:

  

Finalize方法不应引用任何其他对象。

http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=VS.90).aspx

在应用程序退出时收集对象之前,收集引用的托管对象完全有可能被收集。

<强>更新

如果您需要在应用程序退出时清理托管资源,则可以挂钩AppDomain的ProcessExit事件,而不是依赖于终结器执行的非确定性行为。

.NET Console Application Exit Event

答案 1 :(得分:1)

您应该使记录器实现IDisposable,并在using块中使用它。这意味着它将被确定性地处置,而现在它被不确定地破坏了。

错误的原因是您的流有时会在记录器之前关闭,因为Exit基本上会破坏所有内容(非确定性)并退出。您应该使用确定性模式(IDisposable)来避免这种情况。

实际上,析构函数在C#中很少有用,原因在于它们是非确定性的。它们仅用于释放非托管资源。

此外,实施IDisposable可能会使使用单身人士变得不方便。我个人认为最好创建一个在整个程序中使用并在最后处置的实例,而不是一个明确的单例。

答案 2 :(得分:1)

正如其他人已经明确指出的那样,您不应该尝试从记录器类的终结器中访问_logFile对象。您不应该访问终结器中的任何其他对象,因为垃圾收集器可能已经将它们擦除了。

我认为你可以通过几个简单的步骤来避免你的问题:

  1. 摆脱现有的终结者。

  2. 每次写入后执行_logFile.Flush,而不是等到记录器对象生命周期结束时才可能已经太晚了。

    刷新日志文件流经常对我来说是合法的,因为拥有日志的重点是使用它来查找和处理发生错误的情况。如果您的进程突然因特殊情况而终止,您的日志应尽可能完整;因此,频繁刷新日志流缓冲区似乎是一件明智的事情。

  3. 让您的记录器工具IDisposablethis MSDN Magazine article将向您解释如何完成此操作)并从那里关闭您的日志文件流。

答案 3 :(得分:0)

我遇到了同样的问题,我的解决方案如下:

  1. 在您的班级的构造函数中创建FileStream时立即使用GC.SuppressFinalize。这使您负责清理流
  2. 关闭班级Dispose()中的信息流
  3. public class LogFileEventListener : IDisposable
    {
        private bool disposed = false;
        private FileStream fileStream;
    
        public LogFileEventListener(string path)
        {
            //Opens a new file stream to log file
            this.fileStream = new FileStream(path, FileMode.Append, FileAccess.Write);
            GC.SuppressFinalize(this.fileStream);
        }
    
        /// <summary>Finalize the listener</summary>
        ~LogFileEventListener() { this.Dispose(); }
    
        /// <summary>Disposes the listener</summary>
        public override void Dispose()
        {
            try
            {
                if (!this.disposed)
                {
                    /* Do you stuff */
    
                    //Close the log file
                    if (this.fileStream != null)
                    {
                        this.fileStream.Close();
                        this.fileStream = null;
                    }
    
                    base.Dispose();
                }
            }
            finally
            {
                this.disposed = true;
                GC.SuppressFinalize(this);
            }
        }
    }
    

答案 4 :(得分:-2)

StreamWriter很可能在其他位置被关闭。 尝试在你的单例构造函数中创建一个额外的StreamWriter,写几次(确认它正在工作),然后在调用close之前再次在析构函数中写入它(close也会刷新)。

如果上述方法有效,那么您将知道其他一些代码正在关闭您的日志。如果它不起作用,那么你就会知道它是一个.NET的东西(可能与引用变量的方式/位置有关)。

根据documentation,您应该能够通过将StreamWriter放在基类中来解决此问题。这当然不适合你,因为你的测试用例不是标准的终结,而是一个程序退出,这意味着.NET在它想要的时候做它想做的事情。相反,你应该捕获退出事件,处理这个类,然后返回,以保证事情按正确的顺序处理。如果程序因错误而中止,您还应检查终结器中是否已关闭StreamWriter