How to prevent my Timer from GC collecting before its callback excuted?

后端 未结 6 704
情深已故
情深已故 2021-01-26 06:51

I need to create a bunch of timer as local variable to do something like:

void Foo()
{
    Timer t = new Timer(myTimerCallback,null,1000,Timeout.Infinite);
}
         


        
6条回答
  •  情深已故
    2021-01-26 07:22

    A. System.Threading vs System.Timers

    It's worth pointing out that the keeping reference issue is specific to System.Threading.Timer and not System.Timers.Timer.


    B. Threading.Timer's Timer(TimerCallback callback) constructor

    This is not a solution to your problem but I think it's still relevant to the topic.

    System.Threading.Timer's Timer(TimerCallback callback) constructor (the one that doesn't take the dueTime and others) uses this for state which means that the timer will keep a reference to itself and this means that it will survive garbage collection.

    public Timer(TimerCallback callback)
    {
        (...)
        TimerSetup(callback, this, (UInt32)dueTime, (UInt32)period, ref stackMark);
    }
    

    Example

    Timer 'C' is created using Timer(TimerCallback callback) and it just keeps going after GC.Collect().

    Output

    A
    B
    C
    A
    B
    C
    GC Collected
    B
    C
    C
    B
    B
    C
    

    Code

    class TimerExperiment
    {
        System.Threading.Timer timerB;
    
        public TimerExperiment()
        {
            StartTimer("A"); // Not keeping this timer
            timerB = StartTimer("B"); // Keeping this timer
            StartTimer2("C"); // Not keeping this timer
        }
    
        static System.Threading.Timer StartTimer(string name) {
            return new System.Threading.Timer(_ =>
            {
                Console.WriteLine($"{name}");
            }, null, dueTime: Delay(name), period: TimeSpan.FromSeconds(1));
        }
    
        static System.Threading.Timer StartTimer2(string name)
        {
            //Create the timer using the constructor which only takes the callback
            var t = new System.Threading.Timer( _ => Console.WriteLine($"{name}"));
            t.Change(dueTime: Delay(name), period: TimeSpan.FromSeconds(1));
            return t;
        }
    
        static TimeSpan Delay(string name) 
                => TimeSpan.FromMilliseconds(Convert.ToInt64(name[0])*10);
    
    }
    
    class Program
    {
        static async Task Main(string[] args)
        {
            var withTimers = new TimerExperiment();
    
            await Task.Delay(TimeSpan.FromSeconds(2));
            GC.Collect();
            Console.WriteLine("GC Collected");
            Console.ReadLine();
        }
    }
    

提交回复
热议问题