在C#中,我能否通过终结器阻止对象被垃圾收集?

7

如果finalize方法被调用,是否已经太晚了?

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

基本上,我试图通过让.Net垃圾收集器告诉我何时不再需要对象并将其添加回池中来避免重新发明轮子。问题是我需要一种方式来从析构函数中终止垃圾收集。这是可能的吗?


1
是的,你可以。看一下:https://dev59.com/wHA65IYBdhLWcg3wsg2B - DarthVader
能详细解释一下吗?为什么不直接重用对象甚至不引用它们?我不明白为什么你需要破坏垃圾回收器。 - flindeberg
“中止垃圾回收”是什么意思?您的意思是不想让GC调用对象的终结器吗? - Gabe
为什么不将池分成两组:未使用的对象和当前正在使用的对象。给每个对象一个事件,告诉池它何时被保存。这控制了它属于哪个组。 - Reactgular
@MathewFoscarini,这是我最初做的事情,但不幸的是,我必须成为一个超级成就者,并引入了一个破坏该方法的功能。这是一个高度多线程的应用程序(这就是为什么我通过MySql进行日志记录的原因),因此我希望有一种方式可以使日志条目在逻辑上可排列以便于搜索。我制作了一个功能,每个日志条目都有一个父条目,每个父条目都跟踪它有多少个子项。这意味着即使将日志条目刷新到数据库后,直到所有子项都被处理完毕,它才能够被重复使用。我认为劫持GC是最简单的方法。 - ForeverNoobie
@user1379635 这个怎么样?http://msdn.microsoft.com/zh-cn/library/ff458671(v=vs.110).aspx - Reactgular
3个回答

4

你能做到吗? 能。
你应该这样做吗? 不,这几乎肯定是一个糟糕的想法。

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

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

受管理的VM(例如CLR或JVM)使用的内存分配器非常快。在这些系统中,减慢垃圾回收器速度的事情之一就是使用自定义终结器。为了优化运行时,实际上放弃了一个非常快的操作,转而使用一个更慢的操作。此外,“将对象复活”的语义难以理解和推理。

在考虑使用终结器之前,您应该了解以下文章中的所有内容


3

连接池是几乎所有主要的数据库连接实现已经本地支持的功能,因此没有必要手动处理。您只需为每个操作创建一个新连接,并知道在后台实际上会对连接进行池化。

回答您所问的字面问题,是的。您可以确保对象在完成后不会被GCed。您可以通过从某些“活动”位置创建一个对它的引用来实现这一点。

但这真是个糟糕的主意。看看这个例子:

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命中时,不会第二次完成。


1
@MathewFoscarini:“我正在创建一些代码,将日志记录到MySql数据库。”他真正想要的是一个DB连接池。即使他在技术上试图池化日志对象,显然该日志对象中“昂贵”的部分是DB连接;这就是他想要重复使用的东西。 - Servy
MySQL连接器SDK明确表示不要缓存数据库连接,因为连接器已经为您执行了这个操作。 - Reactgular
1
@user1379635 如果是这样,你最好不要这样做。除非池化对象非常耗时且难以处理,否则自己尝试池化对象几乎总会损害性能。在内存中分配对象需要很少的时间,如果它们的生命周期很短,则对GC的开销几乎没有影响。在任何实现中,如果没有额外昂贵的对象初始化,池化项目的开销都会更高。 - Servy
@user1379635 你确定分配内存是问题所在吗?你是否已经“让你的马跑起来”(http://ericlippert.com/2012/12/17/performance-rant/)了? - flindeberg
在终结器中调用Console.WriteLine是不安全的。 - Sam Harwell
显示剩余8条评论

0

1
这个不起作用。文档说明:“请求系统调用先前已调用SuppressFinalize的指定对象的终结器”。如果先调用了SuppressFinalize(),则终结器将永远不会被调用。即使在msdn上的示例也无法正常工作。 - HelloWorld

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