看起来 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客户端,发布版,在调试器之外),只有System.Threading.Timer
被垃圾回收: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)...
编辑:我已经接受了下面John的答案,但是我想进一步阐述一下。
运行上面的示例程序(在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
正如John在他的答案中指出的那样,两个计时器都会将它们的回调函数 (System.Threading._TimerCallback
) 注册到 GCHandle
表中。就像Hans在评论中指出的,当这样做时,state
参数也会被保留。
正如John所指出的那样,System.Timers.Timer
之所以保持活动状态是因为其被回调引用(作为内部的 System.Threading.Timer
的 state
参数传递);同样,我们的 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 Sternal