7

Possible Duplicate:
Timer, event and garbage collection : am I missing something?

private void TrayForm_Load(object sender, EventArgs e)
{
    System.Timers.Timer HideMeTimer = new System.Timers.Timer(2500);
    HideMeTimer.Elapsed +=  
        delegate(object sender2, System.Timers.ElapsedEventArgs e2)
        {
            this.Invoke(new Action(somestuff));
        };
        HideMeTimer.Start();
        GC.Collect();
}

Can somebody please show me how the compilator will translate this? Or good explanation why the timer keep on ticking after the load event.

(Is not the rule when the last reference is gone, to an variable, the GC will sometime kill the object !)

Community
  • 1
  • 1
Niklas
  • 1,753
  • 4
  • 16
  • 35
  • 1
    Evene if the reference could be collected, which it cannot, **you have the call to collect before the variable goes out of scope.** The collector is not going to collect something that is still in scope. (The jitter is *permitted* to realize that the last reference to the variable is before the call to collect, and allow it to be collected, but *in general* your technique here makes no sense; you shouldn't rely on the jitter telling the collector to collect things that are still in scope! – Eric Lippert Nov 22 '11 at 15:13
  • This question has been asked many times; see the "related" section to the right. The short answer is: timers are special; they keep themselves alive. Otherwise they'd be pretty useless; you presumably made a timer because you want it to keep ticking. – Eric Lippert Nov 22 '11 at 15:18
  • The possible duplicate **does not** answer this question. It refers to `System.Windows.Forms.Timer` which works in a completely different manner than `System.Timers.Timer`. – Brian Gideon Nov 22 '11 at 15:41
  • +1 reopen: the cited duplicate is not the same. – Brian Gideon Nov 23 '11 at 00:15

1 Answers1

15

You need to understand what System.Timers.Timer is doing behind the scenes. It is a wrapper around the System.Threading.Timer class. When the timer starts it creates a new System.Threading.Timer instance and passes it a callback method in the System.Timers.Timer instance. The system references this callback delegate as long as the timer remains enabled.

We also know that delegates keep a reference to an instance of the class containing the target method. This is why your System.Timers.Timer instance is not collected and does not stop automatically. Removing the Elapsed event handler will not solve this problem because that delegate only holds a reference to the TrayForm instance. Again, it is the System.Threading.Timer that is keeping a reference to the System.Timers.Timer. The whole reference chain remains rooted because the system has to reference the System.Threading.Timer for it to work.

Here is the reference chain:

System => _TimerCallback => Callback => System.Threading.Timer => Callback => System.Timers.Timer

When you track this through with Reflector or ILSpy you can see that the "System" in the reference chain above comes into play via the TimerBase.AddTimerNative method which is marked MethodImplOptions.InternalCall so we cannot see exactly how the reference is rooted below this point. But, it is rooted nonetheless.

If you disable the timer via Enabled = false or Stop then it will dispose the underlying System.Threading.Timer which should in turn stop referencing the System.Timers.Timer.

Brian Gideon
  • 47,849
  • 13
  • 107
  • 150
  • 1
    -1? If someone sees an incorrect statement in my answer let me know. I have double and triple checked my answer by examining the BCL code via ILSpy, but it is possible that I missed something. – Brian Gideon Nov 22 '11 at 16:01
  • 1
    Not sure who downvoted, but you're spot on. I took a quick peek at the source for `AddTimerNative` and it indeed creates a gc root for the `_TimerCallBack` delegate. – dlev Nov 22 '11 at 16:26
  • @dlev: Thanks for digging into that. I was wondering how `AddTimerNative` was going to "root" that callback. Very informative. – Brian Gideon Nov 22 '11 at 16:39
  • A little bit too late, but for those how are looking for an answer to this question there is a more [detailed answer](http://stackoverflow.com/questions/4962172) (and question). Also it seems that the chain provided is not really correct and the correct one is: System => _TimerCallback => TimerCallback => System.Timers.Timer => System.Threading.Timer – Ilya Chernomordik May 08 '13 at 15:55