【发布时间】: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。
在这两种方法中,我故意为测试目的编写了一个糟糕的代码,并且我没有将 Timer 和 Task 的引用分配给任何变量,以便它们会落入第一代(在垃圾收集中)。
由于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