垃圾邮件GC.KeepAlive(KeyboardHookPointer)有什么问题吗?

时间:2010-04-12 00:15:25

标签: c# garbage-collection keyboard-hook

  

GC.KeepAlive()

     

引用指定的对象,这使得它不符合从当前例程的开始到调用此方法的点的垃圾收集。

不确定GC.KeepAlive除了简单地存储引用之外还做什么,以便垃圾收集器不收集对象。 但是在对象上调用GC.KeepAlive()会永久保持对象不被收集吗?或者你必须经常重新调用GC.KeepAlive()(如果是这样,多久一次)?我想保持我的键盘钩活着。

2 个答案:

答案 0 :(得分:10)

当您为Release目标编译.NET代码时,垃圾收集器非常具有攻击性,也就是说,它具有潜在的可能性。

举个例子:

public void Test()
{
    FileStream stream = new FileStream(....);
    stream.Write(...);
    SomeOtherMethod();
}

在此示例中,一旦调用Stream.Write,此方法就不再使用stream变量,因此被视为超出范围,这意味着{{1} } object现在符合收集条件。如果垃圾收集器在调用FileStream期间运行,则可能会收集流对象。


编辑:在注释中指出@Greg Beech,即使当前正在执行实例方法,也可以收集对象。

例如,假设SomeOtherMethod中的代码如下所示:

FileStream.Write

这里,该方法首先获取包含非托管句柄的内部字段。在此之前不会收集该对象。但是,如果这是对象的最后一个字段,或者是在Write中访问的对象的最后一个实例方法,那么在转换到P / Invoke调用之前,该对象现在有资格进行收集。

由于FileStream可能有一个非托管文件句柄(我说可能,我不知道),它可能也有一个终结器,因此非托管文件句柄有可能在WriteBuffer调用完成之前被关闭。也就是说,如果假设public void Write(byte[] buffer, ...) { IntPtr unmanagedHandle = _InternalHandleField; SomeWindowsDll.WriteBuffer(unmanagedHandle, ...); ... 的实现如上,那么它很可能不是。


如果您希望阻止这种情况,请在调用.Write期间使流对象生效,无论出于何种原因,您需要在以某种方式“使用”对象的调用之后插入代码。如果您不想调用方法或读取对象上的属性,可以使用SomeOtherMethod的人工“使用”方法来执行此操作:

GC.KeepAlive

该方法不执行任何操作,但已使用属性标记,因此不会对其进行优化。这意味着流变量现在在public void Test() { FileStream stream = new FileStream(....); stream.Write(...); SomeOtherMethod(); GC.KeepAlive(stream); } 调用期间使用,并且在此期间不会收集存储在其中的SomeOtherMethod对象。

就是这样。 FileStream不会在任何东西上留下永久性标记,它不会在被调用后改变对象的生命周期,它只是添加在某个时刻“使用”变量的代码,这会阻止垃圾收集器收集对象。 / p>

我了解了这个方法,或者更确切地说,这个方法的重点,一些代码看起来很难的方法:

GC.KeepAlive

请注意,我构造了一个实现public void Test() { SomeObjectThatCanBeDisposed d = new SomeObjectThatCanBeDisposed(); SomeInternalObjectThatWillBeDisposed i = d.InternalObject(); CallSomeMethod(i); } 的对象的实例,在原始情况下,它还实现了一个终结器。然后,我“捕获”了它跟踪的对象,并且一次性对象的设置方式(错误地)是,一旦终结器运行,它也会处理内部对象(这是坏的)。在上面的代码中,一旦提取了内部对象,d就有资格进行收集,一旦收集了对象,它就会通过处理我提取的对象来完成自身。这意味着在调用IDisposable期间,传递给方法的对象死了,我无法理解为什么。

当然,上述代码的修复不是使用CallSomeMethod,而是修复错误的终结器,但如果“内部对象”是非托管资源,那么事情可能会有所不同。

现在,对于键盘钩子,如果将引用存储在根中,如静态字段,也保持活动的对象的成员字段等,则不应该将其收集到蓝色中。

答案 1 :(得分:2)

GC.KeepAlive(以及GC.Collect)不应该在生产代码中使用,因为它不能解决任何问题,只会产生更多的竞争条件。它们仅用于诊断问题。

如果本机代码使用了托管对象,因此垃圾收集器无法看到它仍在使用,那么您应该使用GCHandle.Alloc

编辑:一些支持证据:

GC.KeepAlive并不是很好用,当本地代码的使用在返回之前保证结束时肯定是好的,这不是键盘钩子的情况。