gpt4 book ai didi

c# - 为什么 Timer 让我的对象保持事件状态?

转载 作者:太空狗 更新时间:2023-10-29 21:09:37 24 4
gpt4 key购买 nike

前言:我知道怎么解决问题了。我想知道它出现的原因。请从上到下阅读问题。

我们都(应该)知道,在 C# 中添加事件处理程序会导致内存泄漏。参见 Why and How to avoid Event Handler memory leaks?

另一方面,对象通常具有相似或相关的生命周期,并且没有必要注销事件处理程序。考虑这个例子:

using System;

public class A
{
private readonly B b;

public A(B b)
{
this.b = b;
b.BEvent += b_BEvent;
}

private void b_BEvent(object sender, EventArgs e)
{
// NoOp
}

public event EventHandler AEvent;
}

public class B
{
private readonly A a;

public B()
{
a = new A(this);
a.AEvent += a_AEvent;
}

private void a_AEvent(object sender, EventArgs e)
{
// NoOp
}

public event EventHandler BEvent;
}

internal class Program
{
private static void Main(string[] args)
{
B b = new B();

WeakReference weakReference = new WeakReference(b);
b = null;
GC.Collect();
GC.WaitForPendingFinalizers();

bool stillAlive = weakReference.IsAlive; // == false
}
}

AB 通过事件隐式地相互引用,但 GC 可以删除它们(因为它不使用引用计数,而是使用标记和清除)。

但现在考虑这个类似的例子:

using System;
using System.Timers;

public class C
{
private readonly Timer timer;

public C()
{
timer = new Timer(1000);
timer.Elapsed += timer_Elapsed;
timer.Start(); // (*)
}

private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
// NoOp
}
}

internal class Program
{
private static void Main(string[] args)
{
C c = new C();

WeakReference weakReference = new WeakReference(c);
c = null;
GC.Collect();
GC.WaitForPendingFinalizers();
bool stillAlive = weakReference.IsAlive; // == true !
}
}

为什么GC不能删除C对象?为什么 Timer 使对象保持事件状态?计时器是否通过计时器机制的某些“隐藏”引用(例如静态引用)保持事件状态?

(*) 注意:如果计时器只是创建,而不是启动,则不会出现此问题。如果启动后停止,但事件处理程序未注销,则问题仍然存在。

最佳答案

计时器逻辑依赖于操作系统功能。它实际上是触发事件的操作系统。操作系统又使用 CPU interrupts实现这一点。

操作系统 API,又名 Win32,不包含对任何类型的任何对象的引用。它保存当定时器事件发生时必须调用的函数的内存地址。 .NET GC 无法跟踪此类“引用”。因此,可以在不取消订阅低级事件的情况下收集计时器对象。这是一个问题,因为操作系统无论如何都会尝试调用它,并且会因一些奇怪的内存访问异常而崩溃。这就是 .NET Framework 将所有此类计时器对象保存在静态引用对象中并仅在您取消订阅时才从该集合中删除它们的原因。

如果您使用 SOS.dll 查看对象的根目录,您将得到下一张图片:

!GCRoot 022d23fc
HandleTable:
001813fc (pinned handle)
-> 032d1010 System.Object[]
-> 022d2528 System.Threading.TimerQueue
-> 022d249c System.Threading.TimerQueueTimer
-> 022d2440 System.Threading.TimerCallback
-> 022d2408 System.Timers.Timer
-> 022d2460 System.Timers.ElapsedEventHandler
-> 022d23fc TimerTest.C

然后,如果您查看 dotPeek 之类的 System.Threading.TimerQueue 类,您会发现它是作为单例实现的,并且包含一组计时器。

这就是它的工作原理。不幸的是,MSDN 文档对此并不十分清楚。他们只是假设,如果它实现了 IDisposable,那么您应该毫无疑问地处置它。

关于c# - 为什么 Timer 让我的对象保持事件状态?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14216098/

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