垃圾收集器和非一次性类型

Garbage collector and non-disposable types

给出以下代码:

public static void Test() {
    new Timer((x)=> {
       Console.WriteLine(DateTime.Now);
    }, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
}
public static async Task Main(string[] args) {
   Test();
   await Task.Delay(10000);
   GC.Collect();
   GC.WaitForPendingFinalizers();
   GC.Collect();
   await Process.GetCurrentProcess().WaitForExitAsync();
}

一旦 GC.Collect() 命中,我们可以看到 Timer 被收集并且它停止工作,因为它实现了 IDisposable。但是如果我用 Task 替换 Timer,比方说:

public static void Test() {
    Task.Run(async () => {
        while(true) {
           Console.WriteLine(DateTime.Now);
           await Task.Delay(1000);
        }
    });
}

我们可以看到 Task 保留了 运行ning,尽管它的引用没有保留。但是我们知道 Task 并没有实现 IDisposable.

在这两种方法中,为了测试目的,我故意写了一个错误的代码,并且我没有将 TimerTask 的引用分配给任何变量,这样它们就会落入第一代(在垃圾收集中)。

由于Task保持运行ning,这里有几个相关的问题:

1- 将来某个时候垃圾收集器会收集它吗?

2- 只要 OS 不 运行 内存不足,垃圾收集器是否允许 Task 到 运行?

我 运行 进行了一些测试,我看到 Task 实际上保持 运行ning 甚至几个小时,但我需要知道这种行为是否是 gua运行想继续。

System.Threading.Timer 计时器设计为在垃圾收集时停止。因为您没有将计时器对象分配给任何变量,所以它会立即超出范围。没有对计时器对象的引用(无论是在您的代码中,还是在其他地方)。因此,它有资格进行垃圾回收。

参见 https://github.com/microsoft/referencesource/blob/master/mscorlib/system/threading/timer.cs 实现。 Timer class 在内部创建一个 TimerHolder 对象。 TimerHolder 本身有一个终结器 (~TimerHolder),一旦 GC 运行 成为终结器,它最终“关闭”计时器。

请注意,GC 不是确定性的,在其他情况下,计时器可能会 运行 更长(取决于您的对象已被引用多长时间,它可能会移至更高的 GC 世代)。


Task class,整个异步编程 API 比最初看起来要复杂得多。后台有很多工作和管道发生。参见 https://docs.microsoft.com/en-us/dotnet/standard/async

A Task 通常由 TaskScheduler 处理,它与您当前的线程相关联,通常也与其他线程相关联。因此,某处存在对任务对象的引用,这可以防止任务被垃圾回收。至少在任务完成之前。这是一个非常简单的解释。


编辑(经过更多研究):

使用 Task.Delay 创建的任务不由 TaskScheduler 处理(任务本身没有活动工作,它只是在等待,因此安排它没有意义)。

Task.Delay 内部也使用 System.Threading.Timers。但是通过在内部 TimerHolder 对象上使用 GC.SuppressFinalize 简单地抑制最终确定,计时器明确地保持 运行ning。

https://github.com/microsoft/referencesource/blob/master/mscorlib/system/threading/Tasks/Task.cs


您可能已经注意到,这与 IDisposable 的实现无关。