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

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 suggests )中保持事件状态,但显然不是(在上面的代码中注释掉)。

现在,如果我像下面那样更改代码,callback 确实会按预期进行垃圾收集:
Callback callback = new Callback();
callback = null;

.NET 4.5.1 会发生这种情况。

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

最佳答案

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



Release 版本和 Ctrl-F5 恢复了预期的行为。 我敦促@Porges 将此作为答案 发布,我会投票并感谢接受。

作为后续行动,我想介绍以下有趣的行为。现在使用 Release + Ctrl-F5,即使我取消注释代码中的 // GC.KeepAlive(callback) 行,callback 仍然会被垃圾收集。显然,这是因为由于 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 在这里非常重要,可以防止任何像上面这样的优化。

关于c# - 了解局部变量的垃圾收集器行为,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/22495819/

10-13 06:38