为什么计时器使我的对象保持活动状态? [英] Why does a Timer keep my object alive?

查看:13
本文介绍了为什么计时器使我的对象保持活动状态?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

前言:我知道如何解决问题.我想知道为什么它会出现.请从上到下阅读问题.

Preface: I know how to solve the problem. I want to know why it arises. Please read the question from top to bottom.

众所周知,在 C# 中添加事件处理程序会导致内存泄漏.请参阅为什么以及如何避免事件处理程序内存泄漏?

As we all (should) know, adding event handlers can cause memory leaks in C#. See Why and How to avoid Event Handler memory leaks?

另一方面,对象通常具有相似或连接的生命周期,并且不需要取消注册事件处理程序.考虑这个例子:

On the other hand, objects often have similar or connected life cycles and deregistering event handlers is not necessary. Consider this example:

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 可以删除它们(因为它不使用引用计数,而是使用标记和清除).

A and B reference each other implicitly via events, yet the GC can delete them (because it's not using reference counting, but mark-and-sweep).

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

But now consider this similar example:

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对象?为什么定时器让对象保持活动状态?计时器是否通过计时器机制的一些隐藏"引用(例如静态引用)保持活动状态?

Why can the GC not delete the C object? Why does the Timer keep the object alive? Is the timer kept alive by some "hidden" reference of the timer mechanics (e.g. a static reference)?

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

(*) NB: If the timer is only created, not started, the issue does not occur. If it's started and later stopped, but the event handler is not deregistered, the issue persists.

推荐答案

计时器逻辑依赖于操作系统功能.实际上是操作系统触发了事件.操作系统反过来使用 CPU 中断来实现这一点.

The timer logic relies on an OS functionality. It is actually the OS that fires the event. OS in turn uses CPU interrupts to implement that.

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

The OS API, aka Win32, does not hold references to any objects of any kind. It holds memory addresses of functions which it has to call when a timer event happens. .NET GC has no way to track such "references". As a result a timer object could be collected without unsubscribing from the low-level event. It is a problem because OS would try to call it anyway and would crash with some weird memory access exception. That's why .NET Framework holds all such timer objects in the statically referenced object and removes them from that collection only when you unsubscribe.

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

If you look at the root of your object using SOS.dll you will get the next picture:

!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 类,您会发现它是作为单例实现的,并且它保存了一组计时器.

Then if you look at the System.Threading.TimerQueue class in something like dotPeek, you will see that it is implemented as a singleton and it holds a collection of timers.

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

That's how it works. Unfortunately the MSDN documentation is not crystal clear about it. They just assumed that if it implements IDisposable then you should dispose it no question asked.

这篇关于为什么计时器使我的对象保持活动状态?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆