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

后端 未结 6 665
情深已故
情深已故 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:09

    Store them in a member, for example a List<Timer>.

    0 讨论(0)
  • 2021-01-26 07:16

    Your best bet is to store them in a member variable and then dispose of them when you no longer need them.

    0 讨论(0)
  • 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();
        }
    }
    
    0 讨论(0)
  • 2021-01-26 07:24

    It's important to see that GC.KeepAlive() cannot solve your problem. That just extends the lifetime of your object to the statement. You would have to loop or block for as long as the timer needs to stay alive. Also beware that the life time of references in local variables is different in the Debug build. The JIT keeps them life until the end of the method to allow watches to work. Producing tricky "works in debug, doesn't work in release mode" problems.

    You'll have to keep a life reference to the timer object. That's most typically done with a field in a class. Lifetime requirements now pass to the class object, it needs to stay alive for as long as the timer is needed. Not usually a problem. If it is, you'll have to make the reference static.

    0 讨论(0)
  • 2021-01-26 07:30

    You can store them in a collection to keep a reference and in the event method remove the reference from the collection. I think you need to use the state parameter to identify the timer in the collection (pass a reference to the timer or a key in the collection as state parameter in the "new timer" statement).

    Please note that the callback functions are executed on other threads, so you will/may have multiple callback functions running simultaneously. Use locking to ensure that adding/removing references is done safely.

    0 讨论(0)
  • 2021-01-26 07:32

    Why it's impossible to store them? Maybe you can just remove them from that collection when their work is done?

    0 讨论(0)
提交回复
热议问题