繁体   English   中英

为什么计时器不收集垃圾?

[英]Why doesn't the timer get garbage collected?

我写了一个小实验来更好地理解垃圾收集。 场景是这样的:计时器在设定的时间间隔内调用重复事件。 否 object 持有指向计时器的指针。 当 GC 发生时,计时器是否停止调用其事件?

我的假设是,如果有足够的时间和 GC 尝试,计时器将停止调用其事件。

这是我为测试而编写的代码:

using System;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTest;  


[TestClass]
public class TestGC
{

    //starts a repeating timer without holding on to a reference to the timer
    private void StartTimer(Action callback, int interval)
    {
        var timer = new Timer(t => callback());
        timer.Change(interval, interval);
        timer = null;//this probably does nothing, but lets just overwrite the pointer for good measure
    }



    [TestMethod]
    public void TestEventInvoker()
    {
        var count = new ThreadSafe<int>();
        var interval = 100;//time between timer events (ms)
        var totalTime = 50000;//time of the experiment (ms)
        var expectedCalls = totalTime / interval;//minimum number of times that the timer event is invoked if it isn't stopped
        StartTimer(()=>count.Value++,interval);

        //gc periodically to make sure the timer gets GC'ed
        for (int i = 0; i < expectedCalls; i++)
        {
            GC.Collect();
            Thread.Sleep(interval);
        }

        //for debugging
        Console.WriteLine($"Expected {expectedCalls} calls. Actual: {count.Value}");

        //test passes if the timer stops before the time is over, and the minimum number of calls is not achieved
        // the -1 accounts for the edge case where the test completes just before the last timer call had a chance to be executed
        Assert.IsTrue(count.Value < (expectedCalls - 1));
    }


}

我发现计时器继续重复调用其事件,并且增加总时间和 GC 调用次数对停止计时器没有影响。 示例 output:

Assert.IsTrue 失败。
预计有 500 个电话。 实际:546

所以我的问题是:
为什么计时器继续触发?
计时器是否被 GC 处理? 为什么/为什么不?
如果没有,谁有指向计时器的指针?

我发现的最接近的问题是这个,但答案说在这些情况下应该System.Threading.Timer进行 GC。 我发现它没有被 GC'ed。

这是前面提到的ThreadSafe class。 我在最后添加了它,因为它与问题无关。

//basic threadsafe object implementation. Not necessary for a slow interval, but good for peace of mind
class ThreadSafe<T>
{
    private T _value;
    private readonly object _lock = new();
    public T Value
    {
        get
        {
            lock (_lock)
            {
                return _value;
            }
        }
        set
        {
            lock (_lock)
            {
                _value = value;
            }
        }
    }
}

System.Threading.Timer是一些非托管计时器的托管包装器。
在没有调用Dispose的情况下,托管计时器的实例被 GC 处理,但其非托管资源保持活动状态。

创建Timer时,会在内部根据您的Timer创建TimerQueueTimer Timer对此有一个引用,以便您可以继续修改和控制计时器。

System.Threading.TimerQueue class ( Source )的 static 字段s_queue包含对活动TimerQueueTimer的引用(它是间接的,还有另一个包装类),即使您忘记了对创建它的Timer的引用。

如果您检查该链接文件中Timer构造函数及其change方法的源代码,您会发现在change期间引用确实存储在TimerQueue.s_queue中。

编辑:修复了实际持有哪些对象的准确性。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM