Ste*_*ary 71 .net garbage-collection timer
似乎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();
}
}
Run Code Online (Sandbox Code Playgroud)
当我运行这个程序(.NET 4.0 Client,Release,在调试器之外)时,只有System.Threading.TimerGC是:
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)...
Run Code Online (Sandbox Code Playgroud)
编辑:我已经接受了约翰的答案,但我想稍微阐述一下.
当运行上面的示例程序时(在断点处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
Run Code Online (Sandbox Code Playgroud)
正如约翰在他的回答中指出的那样,两个计时器都System.Threading._TimerCallback在GCHandle表中注册了他们的回调().汉斯在评论中指出,这个state参数在完成后也会保持活跃状态.
正如John指出的那样,原因System.Timers.Timer是保持活着是因为它被回调引用(它作为state参数传递给内部System.Threading.Timer); 同样,我们System.Threading.Timer是GC'ed 的原因是因为它没有被回调引用.
添加对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();
}
}
Run Code Online (Sandbox Code Playgroud)
Joh*_*ohn 31
你可以用windbg,sos和.回答这个和类似的问题 !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>
Run Code Online (Sandbox Code Playgroud)
在这两种情况下,本机计时器必须阻止回调对象的GC(通过GCHandle).不同之处在于,在System.Timers.Timer回调的情况下 引用了System.Timers.Timer对象(使用a在内部实现System.Threading.Timer)
小智 8
在查看Task.Delay的一些示例实现并进行一些实验之后,我最近一直在谷歌上搜索这个问题.
事实证明,System.Threading.Timer是否是GCd取决于你如何构建它!
如果构造只有一个回调,那么状态对象将是计时器本身,这将阻止它被GC.这似乎没有在任何地方记录,但没有它,创建火灾和忘记计时器是非常困难的.
我从http://www.dotnetframework.org/default.aspx/DotNET/DotNET/8@0/untmp/whidbey/REDBITS/ndp/clr/src/BCL/System/Threading/Timer@cs上的代码中找到了这个./1 /定时器@ CS
此代码中的注释还指出为什么在回调引用new返回的计时器对象时使用仅回调ctor总是更好,否则可能存在种族错误.