为什么 Finalize/Destructor 示例在 .NET Core 中不起作用?

Why does the Finalize/Destructor example not work in .NET Core?

提问人:fluter 提问时间:6/24/2017 最后编辑:Peter Dunihofluter 更新时间:8/16/2018 访问量:5966

问:

我正在尝试了解终结和析构函数在 C# 中的工作原理,我尝试运行 System.Object.Finalize 示例中的代码(代码复制粘贴,未进行任何更改),但输出与预期不同,它显示析构函数从未被调用。

代码为:

using System;
using System.Diagnostics;

public class ExampleClass
{
   Stopwatch sw;

   public ExampleClass()
   {
      sw = Stopwatch.StartNew();
      Console.WriteLine("Instantiated object");
   } 

   public void ShowDuration()
   {
      Console.WriteLine("This instance of {0} has been in existence for {1}",
                    this, sw.Elapsed);
   }

   ~ExampleClass()
   {
      Console.WriteLine("Finalizing object");
      sw.Stop();
      Console.WriteLine("This instance of {0} has been in existence for {1}",
                    this, sw.Elapsed);
   }
}

public class Demo
{
   public static void Main()
   {
      ExampleClass ex = new ExampleClass();
      ex.ShowDuration();
   }
}

更新:

当我使用 Visual Studio 和 .net framework 4.5 时,代码按预期工作: 输出与示例相同:

The example displays output like the following:
   Instantiated object
   This instance of ExampleClass has been in existence for 00:00:00.0011060
   Finalizing object
   This instance of ExampleClass has been in existence for 00:00:00.0036294

当我使用 dotnet core app 时,代码不起作用: 实际输出为:

PS C:\ws\test> dotnet run
    Instantiated object
    This instance of ExampleClass has been in existence for 00:00:00.0056874

那么,为什么这在 .NET Core 中有所不同呢?

C# .NET .net-core 析构函数

评论

1赞 Peter Duniho 6/24/2017
“这是一个语言功能,对吧?”-- 没有语言功能可以保证会调用终结器。这就是为什么任何代码都不应该假设一个会被调用。终结器严格作为备份,以减轻错误代码造成的危害。
1赞 Peter Duniho 6/24/2017
作为记录,使用您的代码示例,我重现了您描述的问题,不,我不知道为什么 .NET Core 无法在正常程序退出时完成对象。对我来说听起来像是一个错误。该规范似乎相当清晰,因此,除非 .NET Core 的某些内容在设计上在正常程序终止之前“禁止”对象的最终确定,否则 .NET Core 违反了该规范。
1赞 ℍ ℍ 6/24/2017
我认为这是一个功能
1赞 ℍ ℍ 6/24/2017
@fluter - 您引用的规范是关于 C# 语言的。在我看来,它正在超越它的界限,这是关于平台的行为,而不是语言。
2赞 svick 6/24/2017
ECMA C# 规范的最新草案削弱了这一要求。但我认为这还不够,所以我为此创建了一个 csharplang 问题

答:

-3赞 John Wu 6/24/2017 #1

在垃圾回收器运行之前,不会进行终结。垃圾回收不会运行,除非它需要(例如,您的内存不足),或者您强制它运行

尝试添加

System.GC.Collect();

并查看终结器是否在这种情况下运行。

评论

0赞 fluter 6/24/2017
添加不会有什么区别。但除此之外,我的困惑是 MSDN 示例不使用 GC。收集任何一个,但它显示终结器按预期调用。GC.Collect
1赞 Peter Duniho 6/24/2017
GC 应该在程序退出之前运行。根据 C# 规范:“在应用程序终止之前,将调用其所有尚未进行垃圾回收的对象的析构函数”。问题似乎很清楚,是“为什么 .NET Core 不符合规范?”而不是“如何解决此问题?
1赞 svick 6/24/2017
添加确实会有所作为,但仅限于发布模式。这是因为在调试模式下,局部变量在仍在范围内时不会被收集。虽然这并不能真正回答这个问题。GC.Collect()
25赞 svick 6/24/2017 #2

将 Peter Duniho 和 Henk Holterman 评论中的信息汇总在一起,并进一步扩展:

此行为违反了 Microsoft 的 C# 5.0 规范和 Microsoft 的 C# 6.0 规范的当前草案,其中说:

在应用程序终止之前,将调用其所有尚未进行垃圾回收的对象的析构函数,除非此类清理已被禁止(例如,通过调用库方法)。GC.SuppressFinalize

但这不是一个错误,.Net Core 故意偏离了 .Net Framework 行为,如 corefx 问题中所述:

目前,已尽最大努力在关机期间为所有可终结对象(包括可访问对象)运行终结器。为可访问对象运行终结器并不可靠,因为对象处于未定义状态。

...

建议

不要在关机时运行终结器(对于可访问或无法访问的对象)

...

根据该提案,不能保证所有可最终确定的对象都将在关闭之前完成。

大概正因为如此,ECMA 的 C# 5.0 规范削弱了此要求,因此 .Net Core 不会违反此版本的规范:

在应用程序终止之前,实现应尽一切合理努力为其所有尚未被垃圾回收的对象调用终结器 (§15.13),除非此类清理已被禁止(例如,通过调用库方法)。实现应记录无法保证此行为的任何条件。GC.SuppressFinalize

评论

0赞 Hans Passant 6/24/2017
他们禁用了终结器线程超时。嘎嘎。好吧,除了“不要全部做”之外,别无他法。弱酱。
0赞 Peter Duniho 6/25/2017
“ECMA 的 C# 5.0 规范的当前草案”——切线,但是:什么?我查看了链接,似乎这是 C# 5 的规范(规范中没有 C# 6 功能)。ECMA 似乎在 C# 版本上落后了。“.Net Core 不违反此版本的规范”——我不确定我是否同意这一点。这些更改表明了行为可能不同的原因,但没有证据表明 .NET Core 在此处做出了“一切合理的努力”,也没有证据表明该行为已记录在案。也许这就是他们前进的方向,但似乎还没有到那里。:(
1赞 svick 6/25/2017
@PeterDuniho是的,ECMA还有很多工作要做。但是 C# 任务组由 Jon Skeet 领导,所以我相信不会花那么长时间。我同意 ECMA 草案的措辞并不完全符合 .Net Core 行为,这就是我创建这个 csharplang 问题的原因。
1赞 Loudenvier 2/9/2019
可以添加此内容,现在已完整记录:“在 .NET Framework 应用程序中(但不在 .NET Core 应用程序中),当程序退出时,也会调用终结器。learn.microsoft.com/en-us/dotnet/csharp/programming-guide/......
0赞 Beeeaaar 12/14/2022
@PeterDuniho 不错的努力,但看起来您的尝试所发生的一切只是他们进一步更改了规格以匹配实现,而不是相反。我认为线程问题论点非常薄弱,因为它不会在“关闭开始”之前为终结者做任何以前没有做过的事情。感谢您的尝试。