- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
看来 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 客户端、Release、调试器外部)时,只有 System.Threading.Timer
被 GC 处理:
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
正如 John 在他的回答中指出的那样,两个计时器都在 GCHandle
表中注册了它们的回调 (System.Threading._TimerCallback
)。正如 Hans 在评论中指出的那样,完成此操作后,state
参数也保持事件状态。
正如 John 指出的,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();
}
}
最佳答案
您可以使用 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>
在这两种情况下, native 计时器都必须阻止回调对象的 GC(通过 GCHandle)。不同之处在于 System.Timers.Timer
的情况回调引用 System.Timers.Timer
对象(使用 System.Threading.Timer
在内部实现)
关于.net - 为什么 System.Timers.Timer 能在 GC 中幸存下来,而 System.Threading.Timer 却不能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4962172/
我是一名优秀的程序员,十分优秀!