0

I normally avoid using local variables to hold references to timers because when the local variable goes out of scope the timer gets garbage collected and stops firing. However, I ran into a bit of code that seems to get around this problem by referencing the timer itself through a closure in a lambda expression that is attached to the Elapsed event.

{
    var bkgTimer = new System.Timers.Timer(timeDelay) {AutoReset = false};
    bkgTimer.Elapsed += (sender, args) => Handler(e, bkgTimer);
    bkgTimer.Start();
}

Then later, in the Handler:

private void Handler( ... timer)
{
    ...
    timer.Dispose();
}

It appears to work. Are there any problems with this approach? I wondered if it could lead to a memory leak since I don't understand what the lifetime of the lambda closure is controlled by.

SteveM
  • 305
  • 1
  • 2
  • 12
  • This may be your answer: http://stackoverflow.com/questions/2311027/timer-event-and-garbage-collection-am-i-missing-something. Thomas demonstrates how the Timer holds a reference to itself. – Pieter Geerkens Jun 02 '13 at 01:41
  • @PieterGeerkens : That's a Forms.Timer. This is about a Timers.Timer. – spender Jun 02 '13 at 01:44
  • 1
    There is a Timer class mixup in this question too, it is System.Threading.Timer that has the GC problem, not System.Timers.Timer. – Hans Passant Jun 02 '13 at 12:29
  • I wasn't aware of the difference, is there a link you can post on the topic? – SteveM Jun 02 '13 at 13:33
  • Here it is explained in more detail: http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer – lxa Mar 02 '14 at 21:59

1 Answers1

2

An object cannot avoid GC simply by referencing itself — that's the key distinction between true garbage collectors and reference-counting techniques. If the GC cannot reach it from a root, it will collect it.

Call GCHandle.Alloc to prevent collection, and call GCHandle.Free in the Handler method to prevent leakage.

The advice in the documentation to use GC.KeepAlive only applies to long-running methods. If the method exits immediately, as yours appears to do, GC.KeepAlive won't have the desired effect.

Incidentally, you shouldn't need a separate handler method:

var bkgTimer = new System.Timers.Timer(timeDelay) {AutoReset = false};
var timerHandle = GCHandle.Alloc(bkgTimer);
bkgTimer.Elapsed += (sender, args) => {
    …
    timerHandle.Free();
    bkgTimer.Dispose();
};

bkgTimer.Start();
Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365