为什么我的Close函数没有被调用?

10
 class Program : CriticalFinalizerObject
    {
        static void Main(string[] args)
        {

            Program p = new Program();
            TextWriterTraceListener listener = new TextWriterTraceListener(@"C:\trace.txt");
            Trace.Listeners.Clear(); // Remove default trace listener
            Trace.Listeners.Add(listener);
            Trace.WriteLine("First Trace"); // Generate some trace messages
            Trace.WriteLine("Perhaps last Trace.");

        }

        ~Program()
        {
            Trace.Close();
        }
    }

我得到的文件大小为0。

由于我从CriticalFinalizerObject派生,所以应该执行finalizer。

我不想在finalizer之外使用Trace.Close()

编辑

在@eric Lippert的回复之后:我重新编辑了代码,尝试将其匹配到:受限执行区域(但仍然没有成功)。

  [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
    class Program : CriticalFinalizerObject
    {

        static void Main(string[] args)
        {
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
            }
            catch (Exception e)
            {
            }
            finally
            {
                Program p = new Program();
                TextWriterTraceListener listener = new TextWriterTraceListener(@"C:\trace1.txt");
                Trace.Listeners.Clear();
                Trace.Listeners.Add(listener);
                Trace.WriteLine("First Trace");
                Trace.WriteLine("Perhaps last Trace.");
            }
        }

        ~Program()
        {
            Trace.Flush();
        }
    }

我只是“猜测”最终器不会被调用,因为程序即将关闭。 - Tigran
@Tigran 这就是为什么有 CriticalFinalizerObject 的存在... 以确保它被调用... - Royi Namir
@pstrjds,我已经将close更改为flush,但在析构函数中仍然无法正常工作... - Royi Namir
不要将Close更改为Flush,在主函数中调用Trace.Flush。这样数据在Close调用之前肯定会被刷新。 - pstrjds
我不想在终结器之外使用Trace.Close()。这让我想起了http://www.youtube.com/watch?v=XIX0ZDqDljA。 - Greg D
显示剩余10条评论
3个回答

11

正如文档明确指出的那样:

在从 CriticalFinalizerObject 类派生的类中,公共语言运行时 (CLR) 保证所有关键终结器代码都将有机会执行,前提是终结器遵循 CER 的规则,即使在 CLR 强制卸载应用程序域或中止线程的情况下。如果终结器违反了 CER 的规则,则可能不会成功执行。

你的终结器是否遵循受限执行区域的所有规则?

更新:

你已经更新了代码,试图使其遵循受限执行区域的规则,但我完全没有看到你正确地这么做的任何证据。规则非常清楚;在受限执行区域中的终结器绝不能执行以下任何操作:

  • 分配内存
  • 装箱值类型
  • 获取锁定
  • 调用任意虚拟方法
  • 调用任何缺乏可靠性约定的方法

你的终结器是否执行其中任何一项操作?如果执行了,那么 CLR 没有必要满足你始终运行终结器的愿望。

此外:暂且不论受限执行区域,因为你的程序现在甚至还不是线程安全的。你已经在程序中写入了一个讨厌的竞态条件。

什么阻止即时编译器在跟踪开始之前收集 p ?没有!即时编译器知道 p 不会再使用,并且有权立即在其分配后将其回收。刷新可以在终结器线程上的任何时间发生,包括在写入发生之前或其中任意一部分中间。


3
如果你想这个问题有答案的话,Eric,你应该告诉我们答案...;-) - Chris Dickson
提示:它并不会。考虑阅读CER规则:http://msdn.microsoft.com/en-us/library/ms228973.aspx。然后考虑评估规则(有一个方便的项目列表)。特别是Trace.Flush上的ReliabilityContract是什么?也许你正在为错误的事情使用终结器/CER。 - Greg D
3
专业提示:编写可靠的(与CER相关的)代码是具有挑战性的。如果您不完全了解解决领域中的工具,那么您很可能会搞砸它。 - Greg D

10

因为您没有创建Program类的实例。

您可以在此处阅读更多信息:这里

除非通过调用SuppressFinalize使对象免于终结,否则此方法将在对象变得无法访问时自动调用。在应用程序域关闭期间,对未免于终结的对象自动调用Finalize,即使这些对象仍然可访问也是如此。对于给定实例,只会自动调用一次Finalize,除非使用ReRegisterForFinalize等机制重新注册该对象且随后未调用GC.SuppressFinalize。

因此,如果您想要调用终结器,则需要拥有对象的实例。

更新:如果要写入消息,考虑使用Trace.AutoFlush = true;

更新:(为什么不调用Close函数?) 实际上,Close函数是被调用的(如果在其他终结器中没有发生异常情况)。如果您保留默认的TraceListener(删除Trace.Listeners.Clear()),则会看到所有字符串都成功写入到输出窗口中。

问题在于StreamWriter(它是在TextWriterTraceListener内创建的)没有终结器。因此,它不会将所有数据刷新到文件中。您需要做的是:

FileStream file = new FileStream(@"C:\trace.txt", FileMode.OpenOrCreate);
StreamWriter writer = new StreamWriter(file);
GC.SuppressFinalize(file);
GC.SuppressFinalize(file.SafeFileHandle);
var listener = new TextWriterTraceListener(writer);

实际上你需要在终结器中手动关闭文件。


+1。我感觉自己真的很蠢,没有一开始就看到那个。很好的发现。 - kemiller2002
5
能否更新你的代码,展示如何实例化Program类,这样其他人就不会得出同样的结论了吗? - Gabe
3
我的旅程是通过学习GC内部实现来完成的... :) 因此,我宁愿不使用Trace.AutoFlush,而是要了解为什么没有被调用。 - Royi Namir

0
在C#中,你无法确定何时或是否调用了你的终结器,因为GC处理这个过程。因此,如果你有一些需要释放的资源,最好创建一个实现了IDisposable接口的类,并像这样使用它:
using (MyResourceHolder rh = new MyResourceHolder()) {
    // ...
} // rh.Dispose() is called implicitly

在Dispose方法中,您可以调用Trace.Close()。
请参阅http://msdn.microsoft.com/en-us/library/system.idisposable.aspx以获取良好的IDisposable实现。

Jasd,你读了这些线程吗? :) 整个问题是为什么它没有被调用。我知道dispose模式和其他解决方案。谢谢。 - Royi Namir
是的,我已经阅读了它,并告诉你你不能确定终结器是否被调用,有时可能会被调用,有时则不会。因此,你不应该将其用于这种目的。 - Johannes Egger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接