gpt4 book ai didi

.net - 为什么 System.Timers.Timer 能在 GC 中幸存下来,而 System.Threading.Timer 却不能?

转载 作者:行者123 更新时间:2023-12-03 05:24:26 27 4
gpt4 key购买 nike

看来 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/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com