垃圾收集器和非一次性类型
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
.
在这两种方法中,为了测试目的,我故意写了一个错误的代码,并且我没有将 Timer
和 Task
的引用分配给任何变量,这样它们就会落入第一代(在垃圾收集中)。
由于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 的实现无关。
给出以下代码:
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
.
在这两种方法中,为了测试目的,我故意写了一个错误的代码,并且我没有将 Timer
和 Task
的引用分配给任何变量,这样它们就会落入第一代(在垃圾收集中)。
由于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 的实现无关。