簡體   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