是 GC。KeepAlive官方示例有效吗?

Is the GC.KeepAlive official example valid?

提问人:nrofis 提问时间:11/14/2023 更新时间:11/14/2023 访问量:58

问:

我正在查看该方法的官方示例。但我不完全明白为什么那里是必要的。GC.KeepAliveGC.KeepAlive

GC 收集所有不再具有引用的对象。根据我的理解,引用托管指针的变量在方法上下文的整个生命周期内都存在。因此,GC 永远不应该收集它。hrMain

只有当它有一个额外的优化 (*) ,GC 才会收集它,该优化知道即使引用了对象,将来也不会使用它。hr

我试图找到额外优化的证据,但没有找到。我删除了该命令,程序继续按预期运行(在发布版本中,完全优化,未附加调试器)。GC.KeepAlived

我能够通过将 decleration 移动到另一个生命周期较短的方法/上下文来收集 GC。hrhr

    public static void Register()
    {
        MyWin32.HandlerRoutine hr = new MyWin32.HandlerRoutine(Handler);
        MyWin32.SetConsoleCtrlHandler(hr, true);
    }

    public static void Main()
    {
        // Use interop to set a console control handler.
        Register();

        // Give the user some time to raise a few events.
        Console.WriteLine("Waiting 30 seconds for console ctrl events...");

        // The object hr is not referred to again.
        // The garbage collector can detect that the object has no
        // more managed references and might clean it up here while
        // the unmanaged SetConsoleCtrlHandler method is still using it.

        // Force a garbage collection to demonstrate how the hr
        // object will be handled.
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        
        Thread.Sleep(30000);

        // Display a message to the console when the unmanaged method
        // has finished its work.
        Console.WriteLine("Finished!");

        Console.Read();
    }

现在,如果没有 ,则在触发事件时会出现错误。 但是,一旦我在方法的上下文中保留了指向堆中委托的指针,如果没有 .GC.KeepAliveMainKeepAlive

我的问题:

  1. 官方示例有效吗?因为它即使没有GC.KeepAlive
  2. 额外的优化(上面标有(*))真的存在吗?如果是这样,在哪些 .NET 版本中?因为我看不到它(在 .NET 6 中测试)
C# .NET 垃圾回收

评论

0赞 Panagiotis Kanavos 11/14/2023
该示例有效。 是一个变量,这意味着它是孤立的,并且一旦方法退出,它就有资格进行垃圾回收。 防止这种情况。您看不到此内容的唯一原因是因为在程序持续时间内。 完全。您试图过多地阅读一个简短的示例,该示例旨在展示如何调用方法hrGC.KeepAlive(hr)Mainmethod/context that has shorter lifespan.
1赞 PMF 11/14/2023
若要查看错误,除了使用发布版本之外,可能还必须调用。主要方法只是短路以触发 GC,即使 hr 符合收集条件。GC.Collect()
0赞 nrofis 11/14/2023
@PanagiotisKanavos这就是为什么我要问这个例子是否有效。我的意思是,为什么他们没有像我一样将注册拆分为另一个功能?因为在那个特定的例子中,你并不真正需要 GC。保持活力
1赞 Panagiotis Kanavos 11/14/2023
这是一个文档示例,旨在显示函数调用,而不是指导文档,但它实际上是完全有效的。请改为查看这篇博文,其中解释了本机调用被视为弱引用。即使在 doc 示例中,之后也不再使用,因此它被认为是孤立的。放在最后会创建一个强烈的参考点,这意味着不被视为孤儿......除非您使用hrMyWin32.SetConsoleCtrlHandler(hr, true);GC.KeepAlive(hr);hrhrasync
1赞 Charlieface 11/14/2023
这回答了你的问题吗?收集仍在范围内的对象 - GC。收集

答:

0赞 JonasH 11/14/2023 #1

据我了解,引用托管指针的 hr 变量在 Main 方法上下文的整个生命周期内都存在。因此,GC 永远不应该收集它。

这是错误的。允许 GC 在最后一次使用引用后立即收集对象。完全有可能在运行同一对象的方法时收集对象。

只有当它有一个额外的优化 (*) 时,GC 才会收集它,它知道即使 hr 引用了该对象,它也不会在将来被使用。

这更正确,我不确定它是否在语言文档中指定。但是在KeepAlive的文档中对此进行了很好的解释。也就是说,GC 无法跟踪原生代码“拥有”的引用,因此可能会过早地收集对象。

我试图找到额外优化的证据,但没有找到。我删除了 GC。KeepAlived 命令,程序将继续按预期运行(在发布版本中,完全优化,未附加调试器)。

我的猜测是,这是由于自创建此示例以来 .Net 中的各种更改。运行时发生了很多可能影响集合的情况。我不能说变化的具体原因导致了这种情况,也可能不是完全确定的。

这不会使示例所要演示的原则无效,但如果该示例无法再演示实际问题行为,则确实会使该示例变得相当糟糕。