Threading.Timer阻止GC收集 [英] Threading.Timer prevents GC collection

查看:89
本文介绍了Threading.Timer阻止GC收集的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了内存泄漏问题,我想知道是否有人可以告诉我我做错了什么(或者错过了哪些Microsoft错误).下面是一个示例应用程序,演示了此问题.调用TestCollectTimer.Test()来运行示例.

问题是,无论创建了多少个"MyTimerData",还是调用了多少次GC.Collect(),在应用程序关闭之前,永远不会调用MyTimerData的终结器.

I''m having a memory leak problem, and I''m wondering if anyone can tell me what I''m doing wrong (or what Microsoft bug I missed). Below is a sample application that demonstrates the problem. Call TestCollectTimer.Test() to run the sample.

The problem is, no matter how many "MyTimerData" are created, or how many times GC.Collect() is called, the finalizer of MyTimerData is never called until the application shuts down.

class TestCollectTimer
{
    public static void Test()
    {
        for (int index_A = 0; index_A < 100000; index_A++)
        {
            MyTimerData mtd = new MyTimerData();
            mtd = null;
        }

        GC.Collect();
        Thread.Sleep(2000);
        GC.Collect();

        Form f = new Form();
        f.ShowDialog();
    }
}

class MyTimerData
{
    public System.Threading.Timer m_timer;

    public MyTimerData()
    {
        this.m_timer = new System.Threading.Timer(
            new System.Threading.TimerCallback(this.TimerCall),
            null,
            System.Threading.Timeout.Infinite,
            System.Threading.Timeout.Infinite);
    }

    ~MyTimerData()
    {
        MessageBox.Show("Collect My Timer Data");
    }

    public void TimerCall(object o) { }
}



谢谢您的帮助.



Thank you for your help.

推荐答案

尝试这种方法,它将起作用.

该方法必须是静态的...
相反,如果使用非静态方法并使用this.NameMethod引用它,则System.Threading.TimerCallback将保留对每个MyTimerData对象的引用.因此,尽管您使用了mtd=null指令,但直到应用程序关闭,它才是免费的.

您的示例是循环引用:
mtd => m_timer =>新的System.Threading.TimerCallback(...)=> mtd.TimerCall(o){}

这是工作代码:

Try it this way and it''ll work.

The method must be static...
Conversely, if you use a non static method and you reference it using this.NameMethod, the System.Threading.TimerCallback will keep a reference to each MyTimerData object. So, despite your mtd=null instruction, it won''t be free until the closing of the applicaton.

Your example is a circular reference:
mtd => m_timer => new System.Threading.TimerCallback(...) => mtd.TimerCall(o){}

here''s the working code:

class TestCollectTimer
{
    public static void Test()
    {
        for (int index_A = 0; index_A < 100000; index_A++)
        {
            MyTimerData mtd = new MyTimerData();
            mtd = null;
        }

        GC.Collect();
        Thread.Sleep(2000);
        GC.Collect();

        Form f = new Form();
        f.ShowDialog();
    }
}

class MyTimerData
{
    public System.Threading.Timer m_timer;

    public MyTimerData()
    {
        this.m_timer = new System.Threading.Timer(
            new System.Threading.TimerCallback(TimerCall),
            null,
            System.Threading.Timeout.Infinite,
            System.Threading.Timeout.Infinite);
    }

    ~MyTimerData()
    {
        MessageBox.Show("Collect My Timer Data");
    }

    public static void TimerCall(object o) { }
}


无论如何,问题在于,这不是给计时器提供数据的好方法.您不应该将计时器放在包含数据的类中.

这样的东西会更好

Edited: Anyway, the problem is that this is not a good way to give the timer his data. You shouldn''t put the timer inside the class that contains the data.

Something like this would be better

class TestCollectTimer
    {
        public static void Test()
        {
           MyTimerData data = new MyTimerData();
           data.MyData = 42; //for example...

           System.Threading.Timer myTimer = 
           new System.Threading.Timer(
                new System.Threading.TimerCallback(TimerCall),
                data, //here you pass parameters to the timer
                System.Threading.Timeout.Infinite,
                System.Threading.Timeout.Infinite);
            }
        }
        public static void TimerCall(object o)
        {
            MyTimerData data = (MyTimerData)o;
            //do what you want
        }
    }

    class MyTimerData
    { 
        public int MyData {get; set;}

        ~MyTimerData()
        {
            //...finalize if you have to...
        }
    }


垃圾回收器的存在给人以无限内存的错觉:
您不断分配和分配,GC将自动释放它知道将不再使用的内存.

但是,在您的示例中,所有这些对象将再次使用-在下一个TimerCall()中将需要它们.
计时器事件处理程序不能被垃圾回收,因为它仍在计时器的下一个滴答中使用!
您可以想到一个包含所有活动计时器的全局列表-活动计时器永远不会被垃圾回收.如果它以任何其他方式起作用,则程序的语义(您获得多少个计时器调用)将取决于GC的确切运行时间-这显然是不可取的.

要解决此问题,您必须停止计时器(timer.Dispose())-这会将其从活动计时器列表中删除,并使GC可以收集计时器,事件处理程序和相关对象.

实际上,以上答案仅适用于System.Timers.TimerSystem.Windows.Forms.Timer.

System.Threading.Timer具有一个奇怪的属性,即它不能使自己保持活动状态,而只能使委托保持活动状态.如果从委托人那里返回了到计时器的引用,则与使计时器保持活动状态具有相同的效果.否则,计时器将在计时器对象变得不可访问之后某个时间停止(因此您仍然可以得到不良行为).

在您的示例中,有参考链:
static list of timer callbacks => _TimerCallback => TimerCallback => MyTimerData => Timer

计时器将在其终结器中停止运行,但由于它仍可从其自身创建的静态条目中访问,因此无法进行垃圾回收.
这是.NET世界中的经典泄漏情况:终结器未将静态引用排除为空,因为终结器未运行,因为静态引用尚未被消除. GC无法展望未来,因此也不知道终结器会删除对该对象的最后一个引用.

就是说,如果Microsoft要修复此泄漏(他们不会这样做,因为它可能会破坏依赖于此类无法访问的"计时器来执行其定期工作的程序),那么他们现在就可以像.NET 4.0那样进行修复.引入了一种新型的GC句柄,该程序可以使程序将此场景与GC进行通信:
System.Runtime.CompilerServices.DependentHandle(很遗憾,它是internal;只能通过 ConditionalWeakTable []公开使用 ^ ]类).这是星历 [ ^ ].
The garbage collector exists to give the illusion of infinite memory:
You keep allocating and allocating, and the GC will automatically free memory that it knows will never be used again.

However, in your example, all those objects will be used again - they''re needed on the next TimerCall().
A timer event handler cannot be garbage collected because it is still used on the next tick of the timer!
You can think of a global list that contains all active timers - a timer will never be garbage collected while it is active. If it worked any other way, the semantics of your program (how many timer calls you get) would depend on when exactly the GC runs - which is obviously undesirable.

To solve the issue, you have to stop the timer (timer.Dispose()) - this will remove it from the list of active timers, and will allow the GC to collect the timer, the event handler, and related objects.

actually, the above answer applies only to System.Timers.Timer and System.Windows.Forms.Timer.

System.Threading.Timer has the strange property that it does not keep itself alive, but only keeps the delegate alive. If there''s a reference from the delegate back to the timer, this has the same effect as keeping the timer itself alive. Otherwise, the timer will stop sometime after the timer object became unreachable (so you still can get the undesirable behavior).

In your example, there is the reference chain:
static list of timer callbacks => _TimerCallback => TimerCallback => MyTimerData => Timer

The timer would stop itself in its finalizer, but it can''t be garbage collected because it still is reachable, from the static entry that it created itself.
This is a classical leak situation in the .NET world: a static reference is not nulled out by the finalizer, because the finalizer doesn''t run, because the static reference isn''t nulled out yet. The GC can''t look into the future, so doesn''t know that the finalizer would remove the last reference to the object.

That said, if Microsoft wanted to fix this leak (which they won''t, since it could break programs that depend on such ''unreachable'' timers to perform their periodic work), they could now do it, as .NET 4.0 introduced a new type of GC handle that allows a program to communicate this scenerio to the GC:
System.Runtime.CompilerServices.DependentHandle (unfortunately it''s internal; publically usable only via the ConditionalWeakTable[^] class). This is an implementation of ephemerons[^] in the .NET GC.


这篇关于Threading.Timer阻止GC收集的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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