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

标签 c# garbage-collection keyboard-hook

GC.KeepAlive()

References the specified object, which makes it ineligible for garbage collection from the start of the current routine to the point where this method is called.

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

最佳答案

当您为 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, ...);
    ...

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

而且由于 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,而是修复不正确的终结器,但如果“内部对象”是非托管资源,事情可能不一样了。

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

关于c# - 垃圾邮件 GC.KeepAlive(KeyboardHookPointer) 有什么问题吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2619252/

相关文章:

java - 如何避免垃圾收集器收集对象

windows - 您可以重新映射特定键盘的按键吗?

delphi - 键盘 Hook 过程的问题

c# - LINQ中左外连接和右外连接的实现区别

c# - 为什么序列化DataContract时不能使用lambda?

java - 为什么垃圾收集器在回收内存之前停止所有线程

java - 垃圾收集器如何处理 Java 中的原始数据类型?

c# - 扩展 IDictionary<Key, IEnumerable<Value>>

c# - OpenXML:用 WordprocessingML 中的表替换 <sdt/> 元素

c++ - SetWindowshookEx 有时在 dll 注入(inject)后不起作用