0

What references do System.Timers.Timer instance create in my application if I create/subscribe and start it?

Here are references I know about:

  1. Reference to an instance of our timer that we store in some variable.
  2. Implicit reference when we subscribe to Elapsed event

I assume that there must be some additional references created when we Start our timer. But what are they?

Here is a snippet of code that I experimented with:

class Program
{
    static void Main(string[] args)
    {
        var timers = new List<Timer>();

        Console.WriteLine("Timers to create/start and subscribe: ");

        // step 1
        var count = 1000000;
        for (int i = 0; i < count; i++)
        {
            var timer = new Timer(100000);
            timers.Add(timer);

            // step 2
            timer.Elapsed += Callback;
            timer.Start();
        }

        // step 3: 7mb -> 240mb
        Console.WriteLine("All timers subscribed and started");

        Task.Delay(TimeSpan.FromSeconds(15)).Wait();

        // step 4
        timers.ForEach(t => t.Elapsed -= Callback);
        Console.WriteLine("All timers unsubscribed");

        // step 5: uncomment next two lines to have timers GC collected.
        // timers.ForEach(t => t.Stop());
        // Console.WriteLine("All timers stopped");

        // step 6
        timers.Clear();
        Console.WriteLine("Collection cleared");

        // step 7
        do
        {
            Console.WriteLine("Press Enter to GC.Collect");
            Console.ReadLine();
            GC.Collect();
            Console.WriteLine("GC Collected");
        }
        while (true);
    }

    private static void Callback(object sender, ElapsedEventArgs e)
    {
        Console.WriteLine("callback");
    }
}

Here is what I do here:

  1. Create one million instances of Timer and store them into List
  2. For each timer: subscribe to Elapsed event passing to it a static method reference and then call Start.
  3. Once this is done my application grows in memory from 7mb up to 240mb.
  4. After this I unsubscribe from all timers Elapsed event, hence killing implicit event subscription reference.
  5. OPTIONAL: Stop all timers.
  6. Clear list of timers to remove references to their instances.
  7. GC.Collect to release memory allocated for timers.

If Step 5 is executed(timers are Stopped), then on GC.Collect step I can see that memory occupied by my application reduces from 240mb to about 20mb which means that Timers were recycled correctly.

If Step 5 is omitted, then on GC.Collect step memory usage remains on 240mb level no matter how long I wait and force GC collection. Which actually means that timer instances remain in memory and can not be collected.

From what I know how GC works, my timers should be cleared from memory if they are not accessible from the root(meaning no references).

Are there any other references to my timers that I can eliminate without explicitly calling timer.Stop()?

Is there any other way to prevent memory leak while not calling Stop method?

steavy
  • 1,483
  • 6
  • 19
  • 42
  • Make sure you do this in a release build, or separate out the creation of the list of timers to a separate method. If you're running a debug build, or have the debugger attached, a lot of variables have their life changed to handle debugging and inspecting variables. – Lasse V. Karlsen Nov 06 '17 at 11:01
  • you really create 100k timers? – BugFinder Nov 06 '17 at 11:04
  • 2
    System.Timers.Timer is a pretty awful class, it gets many programmers into trouble. But was most likely added in .NET 1.0 because they feared that they would get in more trouble with System.Threading.Timer. Which has a fairly undesirable property, it gets garbage collected if the program does not keep an explicit reference to it. Storing it in a local variable is the usual mistake. Timers.Timer keeps ticking even if the program does not have a reference to it. So sure, step5 makes an essential difference. – Hans Passant Nov 06 '17 at 11:06
  • 2
    `System.Timers.Timer` is an `IDisposable`. when you use instances of classes that implements the `IDisposable` interface you must dispose them. – Zohar Peled Nov 06 '17 at 11:10
  • @ZoharPeled If type implements `IDisposable` this means that I *should* dispose it, not *must*. If I forget calling `Dispose` then I might have *deferred* gc collection of the instance, but it shouldn't be a memory leak which means that you won't be able to recover this memory. – steavy Nov 06 '17 at 11:18
  • 1
    Even if the garbage collector collects your timer objects, it doesn't call Dispose() for you. – Xaver Nov 06 '17 at 11:20
  • @steavy if an IDisposable is using unmanaged resources, it will only release them when Dispose is called. [see here](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose) - "You implement a Dispose method to release unmanaged resources used by your application. The .NET garbage collector does not allocate or release unmanaged memory." – Zohar Peled Nov 06 '17 at 11:22
  • @ZoharPeled If one uses unmanaged resources then it's a good practice to implement finalizer and Dispose. That's what I would expect from Timer if it was using unmanaged resources. – steavy Nov 06 '17 at 11:26
  • @LasseVågsætherKarlsen Yes, checked this in Release mode without Debugger attached. – steavy Nov 06 '17 at 11:26
  • @HansPassant How can I not store it in variable? I need a reference to manage it. So this was made by design. A strange decision as for me - it's much easier to detect not ticking timer than a memory leak. – steavy Nov 06 '17 at 11:30
  • 1
    You can read this question which should shed some light on your issue: https://stackoverflow.com/q/4962172/5311735 – Evk Nov 06 '17 at 11:32
  • Not so sure what you are trying to say. You just don't need to store it at all, the Elapsed event handler gives the reference back through the *sender* argument. The way it behaves is not fundamentally different from, say, the Form class. As long as the form window is displayed on the screen, the underlying Form object can't be garbage collected. Same for Timer, as long as it is not stopped or disposed it will keep on living and ticking. – Hans Passant Nov 06 '17 at 11:36
  • @steavy actually [Microsoft recommends using `SafeHandle` over overriding `Finalize`.](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/unmanaged) But that's irrelevant to this discussion - My point was that if a class implements the `IDisposable` method you need to dispose it. [You shouldn't care even if the `Dispose` method does nothing](https://stackoverflow.com/questions/46803540/objects-implementing-idisposable-using-another-idisposable/46803623#comment80553995_46803540) - the important thing here is that it implements the `IDisposable` interface. – Zohar Peled Nov 06 '17 at 12:06
  • @ZoharPeled Thanks for the link, will read. I don't say that one can neglect calling `Dispose`. I just think that if you forget to call it than it shouldn't cause a permanent memory leak while we're talking about managed objects. Apparently `Timer` was designed other way. – steavy Nov 06 '17 at 22:02
  • As you can tell from reading the documentation, in contrast to the `System.Threading.Timer` class which requires the calling code to retain a reference to the object, the `System.Timers.Timer` class has its own internal reference that keeps the object reachable while it's running. The behavior you're seeing is entirely normal, expected, and reasonably well documented. See marked duplicate for more discussion on the topic. – Peter Duniho Nov 07 '17 at 00:05

0 Answers0