7

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

 public void Close()
    {
        WriteLogFileFooter();

        _logFile.Flush();

        _logFile.Close();
    }

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

4

5 回答 5

13

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

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

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

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

更新

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

.NET 控制台应用程序退出事件

于 2012-07-05T17:03:23.380 回答
3

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

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

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

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

于 2012-07-05T16:59:10.070 回答
1

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

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

  1. 摆脱当前的终结器。

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

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

  3. 使您的记录器实现IDisposable这篇 MSDN 杂志文章将向您解释这是如何完成的)并从那里关闭您的日志文件流。

于 2012-07-05T17:38:58.077 回答
0

我有同样的问题,我的解决方案如下:

  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);
        }
    }
}
于 2013-02-18T07:53:29.167 回答
-2

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

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

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

于 2012-07-05T17:01:43.047 回答