了解局部变量的垃圾收集器行为

下面是一个非常简单的控制台应用程序(尝试小提琴 ):

using System; using System.Threading; using System.Threading.Tasks; public class ConsoleApp { class Callback { public Callback() { } ~Callback() { Console.WriteLine("~Callback"); } } static void Test(CancellationToken token) { Callback callback = new Callback(); while (true) { token.ThrowIfCancellationRequested(); // for the GC GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); Thread.Sleep(100); } // no need for KeepAlive? // GC.KeepAlive(callback); } public static void Main() { var cts = new CancellationTokenSource(3000); try { Test(cts.Token); } catch (Exception ex) { Console.WriteLine(ex.Message); } GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true); GC.WaitForPendingFinalizers(); Console.WriteLine("Enter to exit..."); Console.ReadLine(); } } 

这里, callback对象在超出Test方法范围之前不会被垃圾收集。

我认为GC.KeepAlive(callback)将需要在Test保持活着(如MSDN建议的那样 ),但显然它不是(在上面的代码中注释掉)。

现在,如果我更改下面的代码, callback会像预期的那样被垃圾收集:

 Callback callback = new Callback(); callback = null; 

这种情况发生在.NET 4.5.1中。

问题 :我错过了什么吗? 我可以依赖这种行为,还是特定于.NET版本的东西?

@Porges的评论很好地解释了一切:

尝试在发布模式下构建和运行它,而不附加调试器。 我得到了预期的行为,但没有在Debug中。

即。 使用Ctrl-F5运行,而不仅仅是F5。 它在.NET 4 / 4.5 / 4.5.1中为我立即收集它。 但是,是的,你不能真正依赖这种行为。

Release版本和CtrlF5带回了预期的行为。 我敦促@Porges发布这个作为答案 ,我会投票并接受谢谢。

作为后续行动,我想介绍以下有趣的行为。 现在使用Release + Ctrl-F5,即使我在代码中取消注释// GC.KeepAlive(callback)行, callback仍然会被垃圾收集。 显然,这是因为编译器将此行识别为由于while (true)循环while (true)无法访问,并且仍然不会在callback上发出强引用。

以下是正确的模式:

 static void Test(CancellationToken token) { Callback callback = new Callback(); try { while (true) { token.ThrowIfCancellationRequested(); // for the GC GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); Thread.Sleep(100); } } finally { GC.KeepAlive(callback); } } 

查看GC.KeepAlive实现也很有趣:

 [MethodImpl(MethodImplOptions.NoInlining)] public static void KeepAlive(object obj) { } 

正如预期的那样,它什么都不做,只是服务器作为编译器生成IL代码的提示,该代码保持对对象的强引用,直到调用KeepAlive为止。 MethodImplOptions.NoInlining在这里非常相关,以防止像上面这样的任何优化。

.NET垃圾收集是不确定的。

您链接到的MSDN页面说明了这一切 – 强调添加:

KeepAlive方法的目的是确保存在对可能被垃圾收集器过早回收的对象的引用。

仅仅因为callback可以在从Test返回范围之前进行垃圾收集并不意味着它将会是。