【发布时间】:2011-06-25 03:11:25
【问题描述】:
System.Timers.Timer 实例似乎通过某种机制保持活动状态,但 System.Threading.Timer 实例不是。
示例程序,带有周期性 System.Threading.Timer 和自动重置 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:
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)...
编辑:我在下面接受了约翰的回答,但我想稍微解释一下。
运行上面的示例程序时(断点位于Sleep),这里是相关对象的状态和GCHandle 表:
!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
正如约翰在回答中指出的那样,两个计时器都在 GCHandle 表中注册了它们的回调 (System.Threading._TimerCallback)。正如 Hans 在他的评论中指出的那样,state 参数在完成后也会保持活动状态。
正如约翰所指出的,System.Timers.Timer 保持活动状态的原因是因为它被回调引用(它作为state 参数传递给内部System.Threading.Timer);同样,我们的System.Threading.Timer 被 GC 的原因是因为它的回调没有引用它。
添加对timer1 回调的显式引用(例如Console.WriteLine("Stayin alive (" + timer1.GetType().FullName + ")"))足以防止GC。
在System.Threading.Timer 上使用单参数构造函数也可以,因为计时器随后会将自身引用为state 参数。以下代码在 GC 之后使两个计时器保持活动状态,因为它们都被 GCHandle 表中的回调引用:
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();
}
}
【问题讨论】:
-
为什么
timer1甚至被垃圾收集?不是还在范围内吗? -
Jeff:范围并不重要。这几乎就是 GC.KeepAlive 方法存在的理由。如果您对挑剔的细节感兴趣,请参阅blogs.msdn.com/b/cbrumme/archive/2003/04/19/51365.aspx。
-
在 Timer.Enabled 设置器中查看 Reflector。请注意它与“cookie”一起使用的技巧,它为系统计时器提供了一个状态对象以在回调中使用。 CLR 知道它, clr/src/vm/comthreadpool.cpp, CorCreateTimer() 在 SSCLI20 源代码中。 MakeDelegateInfo() 变得复杂。
-
@Jeff:这是预期的行为; CLR via C# 中的详细信息并在“当 JIT 编译器行为不同时(调试)”下提到了 on my blog。
-
@StephenCleary 哇 - 精神。我刚刚在一个围绕 System.Timers.Timer 的应用程序中发现了一个错误,该错误在我预计它会死掉之后保持活跃并发布更新。感谢您节省了很多时间!
标签: .net timer garbage-collection