【问题标题】:In C# Can I stop an object from being garbage collected, from the finalizer?在 C# 中,我可以从终结器中阻止对象被垃圾收集吗?
【发布时间】:2014-04-05 00:11:18
【问题描述】:

或者如果达到finalize方法已经晚了?

基本上,我正在创建一些代码来记录到 MySql 数据库。每个日志条目由一个对象表示并存储在一个队列中,直到它在批量插入/更新中被刷新到数据库中。我认为每次我想写一个条目时在堆上创建一个新对象效率低下(特别是因为我可能想在性能敏感区域写一个或两个条目)。我的解决方案是创建一个对象池并重用它们。

基本上,我试图通过让 .Net 垃圾收集器让我知道何时不再需要某个对象并且可以将其添加回池中,从而避免重新发明轮子。问题是我需要从析构函数中止垃圾收集。那可能吗?

【问题讨论】:

  • 是的,你可以。战利品:stackoverflow.com/questions/3680281/…
  • 想详细说明一下?为什么不只是重用对象,甚至从不取消引用它们呢?我不明白你为什么需要破坏垃圾收集器。
  • “中止垃圾回收”是什么意思?你的意思是你不想要 GC 调用的对象终结器?
  • 为什么不将池分成两组。未使用的对象,以及当前正在使用的对象。给每个对象事件以告诉池它何时被保存。它控制它进入哪个组。
  • @MathewFoscarini 这就是我一开始所做的,但不幸的是,我不得不成为一个过度成功的人,我引入了一个打破这种方法的功能。这是一个高度多线程的应用程序(这就是我通过 MySql 进行日志记录的原因),因此我想要一种方法来使日志条目在逻辑上可排列以便于搜索。我做了一个功能,每个日志条目都有一个父条目,每个父条目都会跟踪它有多少个子条目。这意味着即使在日志条目被刷新到数据库之后,它也不能被重用,直到它的所有子条目都已经存在。我认为劫持 GC 是最简单的方法

标签: c# .net


【解决方案1】:

连接池是几乎所有主要的数据库连接实现都已原生支持的功能,因此没有理由手动处理此问题。您将能够简单地为每个操作创建一个新连接,并且知道这些连接实际上会在后台被池化。

要回答您提出的字面问题,是的。您可以确保对象在最终确定后不会被 GC。您只需从某个“实时”位置创建对它的引用即可。

虽然这是一个非常糟糕的主意。看看这个例子:

public class Foo
{
    public string Data;
    public  static Foo instance = null;
    ~Foo()
    {
        Console.WriteLine("Finalized");
        instance = this;
    }
}

public static void Bar()
{
    new Foo() { Data = "Hello World" };
}

static void Main(string[] args)
{
    Bar();
    GC.Collect();
    GC.WaitForPendingFinalizers();
    Console.WriteLine(Foo.instance.Data);
    Foo.instance = null;

    GC.Collect();
    GC.WaitForPendingFinalizers();
}

这将打印出来:

最终确定

你好世界

所以在这里我们最终确定了一个对象,然后我们稍后访问它。然而,问题是该对象已被标记为“已完成”。当它最终再次被 GC 命中时,它没有第二次完成。

【讨论】:

  • 我不认为他在谈论数据库连接。但是记录条目对象。
  • @MathewFoscarini "I'm creating some code to log to a MySql database"。他真正想要在这里汇集的是一个数据库连接。即使他在技术上尝试池化日志对象,显然该日志对象的“昂贵”部分是数据库连接;这就是他试图重复使用的东西。
  • 嗯,MySql 连接器 SDK 明确表示不要缓存 db 连接,因为连接器已经为你做了。
  • 没有。我在这里试图避免的开销是在堆上分配日志对象。在大多数情况下它会分裂头发,但我正在制作一个实时游戏服务器,所以头发应该被分裂。
  • @user1379635 在这种情况下,你几乎肯定最好不要这样做。除非池化对象的创建/处置非常耗时,否则您几乎总是会损害自己尝试池化对象的性能。在内存中分配对象只需要很少的时间,如果它们是短暂的,它们会给 GC 增加很少的开销。在几乎任何实现中,如果没有额外的昂贵的对象初始化,池项的开销都会更高。
【解决方案2】:

您可以在析构函数中重新注册以完成最终确定,如下所示:

~YourClass()
{
   System.GC.ReRegisterForFinalize(this);
}

从那里你可能想要参考一些东西,这样它就不会再次被最终确定,但这是一种方法。

http://msdn.microsoft.com/en-us/library/system.gc.reregisterforfinalize(v=vs.110).aspx

【讨论】:

  • 这不起作用。文档状态:“请求系统为之前调用过 SuppressFinalize 的指定对象调用终结器”。如果首先调用 SuppressFinalize(),则永远不会调用终结器。甚至 msdn 上的示例也不起作用。
【解决方案3】:

你可以吗?是的。
你应该吗?不,这几乎可以肯定是个糟糕的主意。

C# 开发人员应记住的一般规则如下:

如果您发现自己正在编写终结器,那么您可能做错了什么。

完善的托管 VM(例如 CLR 或 JVM)使用的内存分配器非常快。在这些系统中减慢垃圾收集器的一件事是使用定制的终结器。为了优化运行时间,您实际上放弃了非常快的操作,转而使用慢得多的操作。此外,“使物体起死回生”的语义难以理解和推理。

在考虑使用终结器之前,您应该了解以下文章中的一切

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-21
    • 1970-01-01
    • 2015-10-08
    • 1970-01-01
    • 2015-05-04
    • 2014-03-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多