如果任务的回调获取对实例本身的引用,实例是否会被垃圾回收?

Will an instance be garbage collected if a Task's callback get reference to the instance itself?

public class Cls
{
    public object Obj { get; set; }

    public async void Func()
    {
        Task.Run(() =>
        {
            Thread.Sleep(999999999);
            this.Obj = new { };
        });
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        new Cls().Func();
    }
}

请考虑以上代码,如果有意义先忽略。在上面的例子中,我没有将 Cls 的实例存储到任何变量中,似乎没有任何东西引用该实例并且它将是 GC。但是,Func 边有一个Task.Run()Task 的回调函数引用实例的 Obj 属性。在这种情况下我是否仍会被 GC 收集?


我问这个问题是因为在 Microsoft doc 的 SignalR 中它说

Use await when calling asynchronous methods that depend on the hub staying alive.

我不明白为什么只要 Clients.All.SendAsync 里面的东西引用 Hub 本身,Hub 就不会仍然存在...

谢谢。

不,它不会被垃圾回收,因为您在 Task.Run() 中引用了 this(即使您在调用 Thead.Sleep() 之前引用了 this)。

但是,如果您 运行 在 Azure Functions 中使用此代码,例如,框架可能会终止您的应用程序实例并且回调中的代码永远不会 运行(不是因为正在收集垃圾)。

顺便说一句,您可以通过调用GC.Collect() 执行垃圾收集来手动检查它是否被垃圾收集。

您可以使用此代码对其进行测试(运行 例如,在 C# Interactive 中)

static async Task Main(string[] args)
{
    static void CreateObjectAndCallFunc()
    {
        var clsInstance = new Cls();
        clsInstance.Func();
    }

    CreateObjectAndCallFunc();

    Console.WriteLine($"before Task.Delay");
    await Task.Delay(10);
    Console.WriteLine($"after Task.Delay");

    Console.WriteLine($"GC.Collect()");
    GC.Collect();
    Console.WriteLine($"after GC.Collect()");

    Console.WriteLine($"before Task.Delay");
    await Task.Delay(10);
    Console.WriteLine($"after Task.Delay");
}

public class Cls
{
    public Cls() { Console.WriteLine("Cls constructor"); }
    ~Cls() { Console.WriteLine("!!! Cls deconstructor"); }

    public object Obj { get; set; }

    public void Func()
    {
        Task.Run(() =>
        {
            System.Threading.Thread.Sleep(99999);
            this.Obj = new object();
        });
    }
}
await Main(null);

如果你不在Task.Run(..)中引用this.Obj,那么它会输出这样的:

Cls constructor
before Task.Delay
after Task.Delay
GC.Collect()
after GC.Collect()
before Task.Delay
!!! Cls deconstructor
after Task.Delay

但是如果你这样做,它会输出这个:

Cls constructor
before Task.Delay
after Task.Delay
GC.Collect()
after GC.Collect()
before Task.Delay
after Task.Delay