4

GC.KeepAlive()

引用指定的对象,这使得它没有资格从当前例程开始到调用此方法的位置进行垃圾收集。

不太确定 GC.KeepAlive 除了简单地存储引用以使垃圾收集器不收集对象之外做什么。但是在对象上调用 GC.KeepAlive() 会永久阻止对象被收集吗?或者您是否必须每隔一段时间重新调用一次 GC.KeepAlive()(如果需要,多久一次)?我想保持我的键盘钩子活着。

4

2 回答 2

10

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

举个例子:

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

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


编辑:正如@Greg Beech在评论中指出的那样,即使当前正在对其执行实例方法,也可以收集对象。

例如,假设代码FileStream.Write如下所示:

public void Write(byte[] buffer, ...)
{
    IntPtr unmanagedHandle = _InternalHandleField;
    SomeWindowsDll.WriteBuffer(unmanagedHandle, ...);
    ...

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

而且由于 FileStream 可能有一个非托管文件句柄(我说可能,我不知道),它也可能有一个终结器,因此在对 WriteBuffer 的调用完成之前,非托管文件句柄有被关闭的风险。也就是说,如果假设的实现.Write如上,它很可能不是。


如果您希望防止这种情况发生,无论出于何种原因,在调用期间使流对象处于活动状态SomeOtherMethod,您需要在调用之后插入以某种方式“使用”对象的代码。如果您不想调用方法或读取对象上的属性,可以使用人为的“使用”方法GC.KeepAlive来做到这一点:

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

该方法什么都不做,但它已被标记为属性,因此它不会被优化掉。这意味着流变量现在在调用期间一直在使用,SomeOtherMethod并且FileStream在此期间不会收集存储在其中的对象。

这就是它所做的一切。GC.KeepAlive在任何东西上都不会留下永久标记,它不会在被调用后改变对象的生命周期,它只是添加在某个点“使用”变量的代码,这会阻止垃圾收集器收集对象。

我了解了这种方法,或者更确切地说,这种方法的要点,一些看起来像这样的代码的艰难方法:

public void Test()
{
    SomeObjectThatCanBeDisposed d = new SomeObjectThatCanBeDisposed();
    SomeInternalObjectThatWillBeDisposed i = d.InternalObject();
    CallSomeMethod(i);
}

请注意,我构造了一个实现对象的实例IDisposable,并且在原始情况下,它还实现了一个终结器。然后我“捞出”了一个它跟踪的对象,一次性对象的设置方式(不正确)是,一旦终结器运行,它也会处理内部对象(这很糟糕)。在上面的代码中,一旦内部对象被提取,d 就可以被收集,一旦对象被收集,它会通过处理我提取的对象来完成自己。这意味着在调用过程中CallSomeMethod,传递给方法的对象死了,我不明白为什么。

当然,上述代码的修复不是使用GC.KeepAlive,而是修复不正确的终结器,但如果“内部对象”是非托管资源,情况可能会有所不同。

现在,对于您的键盘钩子,如果您将引用存储在根中,例如静态字段、也保持活动状态的对象的成员字段等,那么它不应该被突然收集。

于 2010-04-12T00:23:52.343 回答
1

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

如果您有一个被本机代码使用的托管对象,因此垃圾收集器看不到它仍在使用,您应该使用GCHandle.Alloc

编辑:一些支持证据:

GC.KeepAlive 的用处不是很多,而且只有在本地代码的使用保证在返回之前结束时才绝对有用,而键盘钩子则不是这种情况。

于 2010-04-12T01:02:15.173 回答