【问题标题】:Is it possible to force C# finalizers be run in .NET Core on program exit?是否可以在程序退出时强制 C# 终结器在 .NET Core 中运行?
【发布时间】:2021-03-30 09:59:06
【问题描述】:

我有一个 .NET Core C# 类,它包装了一个非托管指针,它应该在程序退出时与其他资源清理一起被释放。但是,没有调用析构函数。我在调试和发布模式下都试过了。我看到 .NET Core 显然不能保证会运行析构函数,那么推荐的解决方法是什么? IMO 垃圾收集的主要目的是避免让开发人员跟踪引用,所以我觉得这种行为至少可以说令人惊讶。

From MSDN:在 .NET Framework 应用程序中(但不在 .NET Core 应用程序中),程序退出时也会调用终结器。

public Demo { 
  IntPtr _ptr;

  public Demo() 
  { 
    Console.WriteLine("Constructor");
    _ptr = /* P-invoke external function */ 

  ~Demo 
  {
    Console.WriteLine("Destructor");
    /*P-invoke ptr deletion */
  }
}

public static void Main() 
{ 
  Demo demo = new Demo();
  demo = null;
  GC.Collect();
}

程序输出:

Constructor
<...>\Test.exe (process 7968) exited with code 0.

【问题讨论】:

  • 您是否尝试将Demo 声明为静态字段:static Demo demo = new Demo();,然后将其设置为null 并调用GC.Collect()?你的意思是在这里实现IDisposable? (什么版本的 .Net Core?)
  • IMO 正在运行的终结器是一个错误。使用带有IDisposableusing 块,以保证尽可能地处理。请记住,灾难性故障始终是一个问题,例如Environment.FailFasttaskkill 或电源故障
  • 自动清理非托管资源是一个值得称赞的目标,但在 C# 和终结器中实现起来却是出了名的困难,而不是解决这个问题。他们绝对不能保证运行,这只是最大的努力。进一步阅读joeduffyblog.com/2005/04/08/…viva64.com/en/b/0437scale-tone.github.io/2018/04/13/…codeproject.com/Articles/15360/…
  • 除了演示代码,我在 20 年的 .NET 编程中从未编写过终结器(我从 C# 的 alpha 版本开始)。终结器很难正确编写,极难测试,即使您编写了它们,也确实不应该运行(您应该尽可能使用IDisposable 进行清理)。如果你有绝对要求,看SafeHandles

标签: c# .net-core


【解决方案1】:

需要进行更多更改以提高调用终结器的可能性。

顺便说一句,Finalizer 永远不能保证被调用。如果要保证资源释放,请在应用/方法/代码块退出前实现IDisposable并调用Dispose()。此外,要确保在退出代码块之前调用 Dispose()(即使应用程序崩溃,FailFast 和 StackOverflow 除外),请使用 try-finallyusing 语句。

这是一个可以玩的例子。

public class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("[main] Constructing");
        MyDisposable m = new MyDisposable(0);
        MyMethod(1);
        Console.WriteLine("[main] Disposing [object 0]");
        m.Dispose();
        Console.WriteLine("[main] GC Collecting");
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("[main] Done");
        Console.ReadKey();
    }

    private static void MyMethod(int i)
    {
        new MyDisposable(i);
    }
}

public class MyDisposable : IDisposable
{
    private int _id;

    public MyDisposable(int id)
    {
        _id = id;
        Console.WriteLine($"[object {_id}] Constructed");
    }

    private bool disposed = false;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                Console.WriteLine($"[object {_id}] Disposing by Dispose()");
            }
            else
            {
                Console.WriteLine($"[object {_id}] Disposing by ~Finalizer");
            }
            Console.WriteLine($"[object {_id}] Disposed");
            disposed = true;
        }
        else
            Console.WriteLine($"[object {_id}] Already disposed!");
    }

    ~MyDisposable()
    {
        Dispose(false);
    }
}

输出

[main] Constructing
[object 0] Constructed
[object 1] Constructed
[main] Disposing [object 0]
[object 0] Disposing by Dispose()
[object 0] Disposed
[main] GC Collecting
[object 1] Disposing by ~Finalizer
[object 1] Disposed
[main] Done

有人读到:Using objects that implement IDisposable

【讨论】:

  • usingIDisposable 调用 Dispose(),有或没有 GC.WaitForPendingFinalizers()。此外,没有 IDisposableGC.WaitForPendingFinalizers() 不会强制运行终结器 - 我不清楚您认为它会做什么。
  • Finalizer 永远不能保证被调用。 应该是双粗体。 @evo8198 这里的重点是,如果要处理,请使用IDisposable。你的第二点根本不正确。终结器与IDisposable 没有本质上的关系,除了它们的代码通常只是调用IDisposable.Dispose,这就是为什么Dispose 应该包含GC.SuppressFinalize
  • @evo8198 我刚刚展示了如何让 GC 调用 ~Demo。举个例子。但是我没有告诉任何关于 GC 方法显式调用和IDisposable 之间的连接。
  • @aepot,在我的评论中,我说GC.WaitForPendingFinalizers() 实际上并没有强制调用~Demo()。我刚刚测试过。
  • @aepot,谢谢,这就是问题所在 - 没有为我调用终结器,因为我在与对象相同的范围内调用 GC 方法。我希望将对象设置为 null 就足够了,但事实并非如此。
【解决方案2】:

官方Cleaning up unmanaged resources 状态:

如果您的类型使用非托管资源,您应该执行以下操作:

实施处置模式。 (...)

如果您类型的使用者忘记调用 Dispose,请提供一种释放非托管资源的方法。有两种方法可以做到这一点:

  • 使用安全句柄来包装您的非托管资源。这是推荐的技术。安全句柄派生自 System.Runtime.InteropServices.SafeHandle 抽象类,并包含一个健壮的 Finalize 方法。使用安全句柄时,只需实现 IDisposable 接口并在 IDisposable.Dispose 实现中调用安全句柄的 Dispose 方法。如果未调用其 Dispose 方法,则垃圾收集器会自动调用安全句柄的终结器。

(...)

Implement a Dispose method 包含:

  • '为使用安全句柄的基类实现 dispose 模式的一般模式';和
  • '实现覆盖Object.Finalize的基类的dispose模式的一般模式@'。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-26
    • 1970-01-01
    • 1970-01-01
    • 2020-07-04
    • 1970-01-01
    • 2019-03-20
    • 1970-01-01
    • 2012-03-02
    相关资源
    最近更新 更多