【问题标题】:Garbage collector and non-disposable types垃圾收集器和非一次性类型
【发布时间】:2022-01-05 07:25:13
【问题描述】:

给出以下代码:

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。但是,如果我将 Timer 替换为 Task,假设:

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

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

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

由于Task 一直在运行,这里有几个相关的问题:

1- 将来某个时间会被垃圾收集器收集吗?

2- 只要操作系统没有内存不足,垃圾收集器是否允许Task 运行?

我进行了一些测试,发现Task 实际上甚至可以运行几个小时,但我需要知道这种行为是否可以保证继续。

【问题讨论】:

  • GC 对IDisposable 什么都不做,它甚至对此一无所知。 Task 肯定存储在 ThreadPool/BCL 内部以进行调度/执行,因此在完成之前不会进行 GC 处理。至于Timer - 我不确定这里使用了哪个确切的计时器以及它是如何在内部实现的。可能它确实在任何地方都没有托管引用并且是 GC-ed。
  • @ArnoldZahrneinder 正如 Serg 所说,它与 IDisposable 无关。这完全取决于谁引用了它。该任务被线程池引用。
  • 一个Task 对象代表一些可能在未来完成的活动。某些任务与已在线程池上安排的活动相关联(例如通过Task.Run)。其他人与TaskCompletionSource 相关联。 (还有一些代表异步方法调用)请注意,在任何一种情况下,您是否持有 Task 对象与将来要完成的活动无关
  • 即使Task 被收集,也不会影响正在进行的活动。这就是我想让你看到的是一个单独的的东西。
  • 不,GC 不会收集任务,直到它没有运行完成或失败,就像之前发生的那样,对任务的引用存储在 BCL 中。但这是实现细节,您实际上不应该依赖它。无论如何,我相信 GC 无法停止/终止正在进行的任务执行(应用程序关闭的情况除外)。

标签: c# garbage-collection .net-6.0


【解决方案1】:

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

请参阅https://github.com/microsoft/referencesource/blob/master/mscorlib/system/threading/timer.cs 了解实施情况。 Timer 类在内部创建一个 TimerHolder 对象。 TimerHolder 本身有一个终结器 (~TimerHolder),一旦 GC 运行终结器,它最终会“关闭”计时器。

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


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

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


编辑(经过更多研究):

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

Task.Delay 在内部也使用System.Threading.Timers。但是计时器显式地保持运行,只需在内部TimerHolder 对象上使用GC.SuppressFinalize 抑制终结。

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


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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-20
    • 2015-01-06
    • 1970-01-01
    • 1970-01-01
    • 2015-03-14
    • 1970-01-01
    相关资源
    最近更新 更多