【问题标题】:C# garbage collector seems to be closing my StreamWriter too earlyC# 垃圾收集器似乎过早地关闭了我的 StreamWriter
【发布时间】:2012-07-06 03:10:42
【问题描述】:

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

 public void Close()
    {
        WriteLogFileFooter();

        _logFile.Flush();

        _logFile.Close();
    }

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

【问题讨论】:

  • 你的单例实现是可丢弃的,而来自该实现的 Dispose 就是所谓的 Close 类?
  • 简而言之,你不能这样做。您无法从终结器访问托管对象(例如流),无法保证它将处于何种状态,或者它是否会存在。
  • 我一直想知道为什么人们要重新发明轮子。为什么人们不使用框架中内置的内容来执行日志记录和跟踪?我什至没有提到第三方工具。我只是想知道为什么人们不使用框架中已经存在的东西。出于好奇,仅此而已。
  • 那我邀请你去看看System.Diagnostics.Trace类。
  • 正如大家所说,从终结者那里调用Console.WriteLine 甚至都不安全。

标签: c# .net singleton garbage


【解决方案1】:

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

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

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

另外,实现IDisposable 可能会使使用单例变得不方便。我个人认为最好创建一个在整个程序中使用并在最后处理的实例,而不是显式的单例。

【讨论】:

  • 将 using 块与单例记录器一起使用是没有意义的。您不想在每次使用后丢弃它。
  • 这也没有回答 OP 的问题。
  • 问题是我想使用来自多个类的记录器而不创建多个实例。此外,在 main() 中调用了 Exit(),但不像您所期望的那样在最后调用。它被调用以响应错误。因此,即使我有一个包含我的主要功能的 using 块,我也会遇到同样的错误
  • @user1379635:我认为return; 会是更好的选择,因为它允许您使用IDisposable,而Exit 不是为此而设计的。此外,您可以使用单例模式,并在程序末尾的 finally 块中使用 Dispose 单例。
【解决方案2】:

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

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

根据documentation,您应该能够通过将StreamWriter 放在基类中来解决此问题。这当然对您不起作用,因为您的测试用例不是标准的最终确定,而是程序退出,这意味着 .NET 在需要时做它想做的事情。相反,您应该捕获退出事件,处置此类,然后返回,以确保以正确的顺序处置事物。您还应该检查 StreamWriter 是否已经在终结器中关闭,以防程序由于错误而中止。

【讨论】:

  • 这个问题在cmets上的问题上已经很好地确定了,这不是它。
  • @roken 你有权发表你的意见,但我在 .NET 方面的经验与你对这个问题的假设相矛盾。
  • @Trisped roken 这次是对的。除了垃圾收集器之外,没有任何东西可以关闭我的流编写器。我刚刚写了日志类,还没有外部代码真正知道它。还是谢谢。
  • @Trisped 从某种意义上说,您的答案是准确的,它 is 在其他地方被关闭,但这肯定是由于流试图在终结器中访问. GC 很可能在完成记录器之前完成流(从而关闭它)。这不是我的意见,事实上你不能在终结器中使用流。这里没有矛盾。
  • @roken 他不能使用流很清楚,但为什么仍然不是(至少对我来说)。析构函数必须允许清理非托管资源。因此,这意味着访问本地类变量。类中的对象在对它的引用被删除/终止之前就已经完成是没有意义的。
【解决方案3】:

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

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

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

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

更新

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

.NET Console Application Exit Event

【讨论】:

  • 您的报价断章取义。 “一个对象的 Finalize 方法应该释放该对象拥有的任何外部资源。此外,一个 Finalize 方法应该只释放该对象持有的资源。Finalize 方法不应该引用任何其他对象。”
  • 完全在上下文中。它应该释放资源(非托管资源是指)并且不持有对[托管]对象的引用。我引用的部分没有任何内容使答案不准确。
  • @EricJ。所以简而言之,如何在 StreamWriter 被回收之前编写我的日志页脚?
  • @Eric J. 所以按照你的逻辑,在析构函数中不能访问任何类变量。这样就很难释放外部资源了。
  • @Trisped:您当然可以访问类变量,除非它们是托管对象。终结器的目的是清理 unmanaged 资源。 托管资源由 GC 清理。不是我的逻辑......只是引用 MSDN(并添加一点上下文)。
【解决方案4】:

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

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

  1. 摆脱你当前的终结器。

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

    对我来说,频繁刷新日志文件流似乎是合法的,因为拥有日志的全部意义在于使用它来查找和处理发生错误的情况。如果你的进程因异常情况突然终止,你的日志仍应尽可能完整;因此,经常刷新日志流缓冲区似乎是明智之举。

  3. 让您的记录器实现IDisposablethis MSDN Magazine article 将向您解释这是如何完成的)并从那里关闭您的日志文件流。

【讨论】:

    【解决方案5】:

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

    1. 在类的构造函数中创建FileStream 时,立即使用GC.SuppressFinalize。这使您负责清理流
    2. 关闭类的Dispose()中的流
    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);
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-08-11
      • 1970-01-01
      • 2018-03-09
      • 1970-01-01
      • 2010-09-10
      • 1970-01-01
      • 2016-11-18
      • 1970-01-01
      • 2013-12-03
      相关资源
      最近更新 更多