为什么 System.Timers.Timer 能在 GC 中存活,而 System.Threading.Timer 不能?

Why does a System.Timers.Timer survive GC but not System.Threading.Timer?(为什么 System.Timers.Timer 能在 GC 中存活,而 System.Threading.Timer 不能?)

本文介绍了为什么 System.Timers.Timer 能在 GC 中存活,而 System.Threading.Timer 不能?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

System.Timers.Timer 实例似乎通过某种机制保持活动状态,但 System.Threading.Timer 实例却没有.

It appears that System.Timers.Timer instances are kept alive by some mechanism, but System.Threading.Timer instances are not.

示例程序,带有周期性System.Threading.Timer和自动重置System.Timers.Timer:

Sample program, with a periodic System.Threading.Timer and auto-reset System.Timers.Timer:

class Program
{
  static void Main(string[] args)
  {
    var timer1 = new System.Threading.Timer(
      _ => Console.WriteLine("Stayin alive (1)..."),
      null,
      0,
      400);

    var timer2 = new System.Timers.Timer
    {
      Interval = 400,
      AutoReset = true
    };
    timer2.Elapsed += (_, __) => Console.WriteLine("Stayin alive (2)...");
    timer2.Enabled = true;

    System.Threading.Thread.Sleep(2000);

    Console.WriteLine("Invoking GC.Collect...");
    GC.Collect();

    Console.ReadKey();
  }
}

当我运行这个程序(.NET 4.0 Client,Release,在调试器之外)时,只有 System.Threading.Timer 被 GC'ed:

When I run this program (.NET 4.0 Client, Release, outside the debugger), only the System.Threading.Timer is GC'ed:

Stayin alive (1)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Invoking GC.Collect...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...

编辑:我在下面接受了约翰的回答,但我想稍微解释一下.

EDIT: I've accepted John's answer below, but I wanted to expound on it a bit.

当运行上面的示例程序时(在 Sleep 处有一个断点),下面是相关对象的状态和 GCHandle 表:

When running the sample program above (with a breakpoint at Sleep), here's the state of the objects in question and the GCHandle table:

!dso
OS Thread Id: 0x838 (2104)
ESP/REG  Object   Name
0012F03C 00c2bee4 System.Object[]    (System.String[])
0012F040 00c2bfb0 System.Timers.Timer
0012F17C 00c2bee4 System.Object[]    (System.String[])
0012F184 00c2c034 System.Threading.Timer
0012F3A8 00c2bf30 System.Threading.TimerCallback
0012F3AC 00c2c008 System.Timers.ElapsedEventHandler
0012F3BC 00c2bfb0 System.Timers.Timer
0012F3C0 00c2bfb0 System.Timers.Timer
0012F3C4 00c2bfb0 System.Timers.Timer
0012F3C8 00c2bf50 System.Threading.Timer
0012F3CC 00c2bfb0 System.Timers.Timer
0012F3D0 00c2bfb0 System.Timers.Timer
0012F3D4 00c2bf50 System.Threading.Timer
0012F3D8 00c2bee4 System.Object[]    (System.String[])
0012F4C4 00c2bee4 System.Object[]    (System.String[])
0012F66C 00c2bee4 System.Object[]    (System.String[])
0012F6A0 00c2bee4 System.Object[]    (System.String[])

!gcroot -nostacks 00c2bf50

!gcroot -nostacks 00c2c034
DOMAIN(0015DC38):HANDLE(Strong):9911c0:Root:  00c2c05c(System.Threading._TimerCallback)->
  00c2bfe8(System.Threading.TimerCallback)->
  00c2bfb0(System.Timers.Timer)->
  00c2c034(System.Threading.Timer)

!gchandles
GC Handle Statistics:
Strong Handles:       22
Pinned Handles:       5
Async Pinned Handles: 0
Ref Count Handles:    0
Weak Long Handles:    0
Weak Short Handles:   0
Other Handles:        0
Statistics:
      MT    Count    TotalSize Class Name
7aa132b4        1           12 System.Diagnostics.TraceListenerCollection
79b9f720        1           12 System.Object
79ba1c50        1           28 System.SharedStatics
79ba37a8        1           36 System.Security.PermissionSet
79baa940        2           40 System.Threading._TimerCallback
79b9ff20        1           84 System.ExecutionEngineException
79b9fed4        1           84 System.StackOverflowException
79b9fe88        1           84 System.OutOfMemoryException
79b9fd44        1           84 System.Exception
7aa131b0        2           96 System.Diagnostics.DefaultTraceListener
79ba1000        1          112 System.AppDomain
79ba0104        3          144 System.Threading.Thread
79b9ff6c        2          168 System.Threading.ThreadAbortException
79b56d60        9        17128 System.Object[]
Total 27 objects

正如 John 在回答中指出的那样,两个计时器都在 GCHandle 表中注册了它们的回调 (System.Threading._TimerCallback).正如 Hans 在他的评论中指出的那样,state 参数在完成后也会保持活动状态.

As John pointed out in his answer, both timers register their callback (System.Threading._TimerCallback) in the GCHandle table. As Hans pointed out in his comment, the state parameter is also kept alive when this is done.

正如约翰指出的那样,System.Timers.Timer 保持活动状态的原因是因为它被回调引用(它作为 state 参数传递给内部 System.Threading.Timer);同样,我们的 System.Threading.Timer 被 GC 的原因是因为它的回调没有引用它.

As John pointed out, the reason System.Timers.Timer is kept alive is because it is referenced by the callback (it is passed as the state parameter to the inner System.Threading.Timer); likewise, the reason our System.Threading.Timer is GC'ed is because it is not referenced by its callback.

添加对 timer1 回调的显式引用(例如,Console.WriteLine("Stayin alive (" + timer1.GetType().FullName + ")")) 足以防止 GC.

Adding an explicit reference to timer1's callback (e.g., Console.WriteLine("Stayin alive (" + timer1.GetType().FullName + ")")) is sufficient to prevent GC.

System.Threading.Timer 上使用单参数构造函数也可以,因为计时器随后会将自身作为 state 参数进行引用.以下代码在 GC 之后使两个计时器保持活动状态,因为它们都被 GCHandle 表中的回调引用:

Using the single-parameter constructor on System.Threading.Timer also works, because the timer will then reference itself as the state parameter. The following code keeps both timers alive after the GC, since they are each referenced by their callback from the GCHandle table:

class Program
{
  static void Main(string[] args)
  {
    System.Threading.Timer timer1 = null;
    timer1 = new System.Threading.Timer(_ => Console.WriteLine("Stayin alive (1)..."));
    timer1.Change(0, 400);

    var timer2 = new System.Timers.Timer
    {
      Interval = 400,
      AutoReset = true
    };
    timer2.Elapsed += (_, __) => Console.WriteLine("Stayin alive (2)...");
    timer2.Enabled = true;

    System.Threading.Thread.Sleep(2000);

    Console.WriteLine("Invoking GC.Collect...");
    GC.Collect();

    Console.ReadKey();
  }
}

推荐答案

你可以用 windbg、sos 和 !gcroot

You can answer this and similar questions with windbg, sos, and !gcroot

0:008> !gcroot -nostacks 0000000002354160
DOMAIN(00000000002FE6A0):HANDLE(Strong):241320:Root:00000000023541a8(System.Thre
ading._TimerCallback)->
00000000023540c8(System.Threading.TimerCallback)->
0000000002354050(System.Timers.Timer)->
0000000002354160(System.Threading.Timer)
0:008>

在这两种情况下,本机计时器都必须阻止回调对象的 GC(通过 GCHandle).不同之处在于,在 System.Timers.Timer 的情况下,回调引用 System.Timers.Timer 对象(使用 System.Threading 在内部实现.计时器)

In both cases, the native timer has to prevent GC of the callback object (via a GCHandle). The difference is that in the case of System.Timers.Timer the callback references the System.Timers.Timer object (which is implemented internally using a System.Threading.Timer)

这篇关于为什么 System.Timers.Timer 能在 GC 中存活,而 System.Threading.Timer 不能?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:为什么 System.Timers.Timer 能在 GC 中存活,而 System.Threading.Timer 不能?

基础教程推荐