【问题标题】:Will an instance be garbage collected if a Task's callback get reference to the instance itself?如果任务的回调获取对实例本身的引用,实例是否会被垃圾收集?
【发布时间】:2020-06-19 06:49:56
【问题描述】:
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 收集吗?


我问这个问题是因为在 SignalR 的 Microsoft doc 中声明

在调用依赖于集线器保持活动状态的异步方法时使用等待。

我不明白,只要Clients.All.SendAsync 内部的某些东西引用了Hub 本身,Hub 怎么会不存在...

谢谢。

【问题讨论】:

  • 哪个回调?
  • 你的程序先结束,没有Cls 不会在任务运行时被垃圾回收。只是你打电话给this 的事实阻止了它,除其他外......
  • @Gusman Task.Run 中的 lambda 函数
  • 对于每个方法调用,SignalR 都会创建一个 Hub 类的新实例。在该方法调用结束后,SignalR 将 Dispose 那个 Hub 实例。所以如果你不awaitasync 调用它只会启动它而不等待完成,然后在Hub 实例上调用Dispose。这个 Dispose 直接从 SignalR 框架调用,因此与 GarbageCollection 无关,并且引用了 Hub
  • 因此,您的问题的答案是“不,如果有对 Hub 的有效引用,GC 将不会收集它,但它会被处理掉,因此不再‘活着’ "

标签: c# multithreading async-await garbage-collection


【解决方案1】:

不,它不会被垃圾回收,因为您在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

【讨论】:

  • 谢谢。稍后我会将其标记为已接受的答案。因为我仍然不明白 SignalR 中的 Hub 是如何工作的。似乎如果我不awaitTaskTask 内的回调无法获取对实例的引用,因为它是垃圾收集的。
  • 我对 SignalR 不熟悉,但也许它类似于 Azure Functions 的工作方式。如果您等待任务,则框架知道您需要保留应用程序实例并且不会终止。你需要调查一下,我不知道
  • 谢谢@Artemious 这么清楚我会试一试的!!
猜你喜欢
  • 1970-01-01
  • 2012-07-19
  • 2020-05-11
  • 1970-01-01
  • 2020-05-18
  • 1970-01-01
  • 1970-01-01
  • 2016-02-04
  • 1970-01-01
相关资源
最近更新 更多