2

I'm having a memory leak problem, and I'm wondering if anyone can tell me what I'm doing wrong (or what Microsoft bug I missed). Below is a sample application that demonstrates the problem. Call TestCollectTimer.Test() to run the sample.

The problem is, no matter how many "MyTimerData" are created, or how many times GC.Collect() is called, the finalizer of MyTimerData is never called until the application shuts down.

 class TestCollectTimer
{
    public static void Test()
    {
        for (int index_A = 0; index_A < 100000; index_A++)
        {
            MyTimerData mtd = new MyTimerData();
            mtd = null;
        }

        GC.Collect();
        Thread.Sleep(2000);
        GC.Collect();

        Form f = new Form();
        f.ShowDialog();
    }
}

class MyTimerData
{
    public System.Threading.Timer m_timer;

    public MyTimerData()
    {
        this.m_timer = new System.Threading.Timer(
            new System.Threading.TimerCallback(this.TimerCall),
            null,
            System.Threading.Timeout.Infinite,
            System.Threading.Timeout.Infinite);
    }

    ~MyTimerData()
    {
        MessageBox.Show("Collect My Timer Data");
    }

    public void TimerCall(object o) { }
}

Thankyou for your help

Blorgbeard
  • 101,031
  • 48
  • 228
  • 272
  • Run it in Release mode, without a debugger attached. See [related question](http://stackoverflow.com/questions/24854797/using-dispose-method/24855214#24855214). – Blorgbeard Jul 22 '14 at 03:51
  • I would assume it's because you've started the timer and haven't stopped it. Running timers keep themselves alive so they don't get garbage collected (because that's better than your timer dying unpredictably). I'm not sure what you're trying to accomplish by passing Timeout.Infinite for both parameters, though -- you're saying "make a timer that will never fire". – Joe White Jul 22 '14 at 03:52
  • 1
    @JoeWhite that contradicts the [docs](http://msdn.microsoft.com/en-us/library/system.threading.timer.aspx): "As long as you are using a Timer, you must keep a reference to it. As with any managed object, a Timer is subject to garbage collection when there are no references to it. The fact that a Timer is still active does not prevent it from being collected." – Blorgbeard Jul 22 '14 at 03:56

2 Answers2

2

In debug mode, all local variables' scopes are artificially extended to the end of their enclosing methods, so they won't be collected before you've finished inspecting them in the debugger.

Your code works as expected when compiled in Release mode and run with no debugger attached.

You can also just move your for-loop into its own method, and then your timers will be eligible for collection after it returns, even in debug mode.

Blorgbeard
  • 101,031
  • 48
  • 228
  • 272
0

What about disposing your timer?

class TestCollectTimer
{
    public static void Test()
    {
    for (int index_A = 0; index_A < 100000; index_A++)
    {
        using(MyTimerData mtd = new MyTimerData())
        {
           //do your stuff here
        }

    }

    GC.Collect();

    Form f = new Form();
    f.ShowDialog();
}
}

class MyTimerData : IDisposable
{
public System.Threading.Timer m_timer;

public MyTimerData()
{
    this.m_timer = new System.Threading.Timer(
        new System.Threading.TimerCallback(this.TimerCall),
        null,
        System.Threading.Timeout.Infinite,
        System.Threading.Timeout.Infinite);
}

public void TimerCall(object o) { }

public void Dispose()
{
    Dispose(true);
}

protected void Dispose(bool disposing)
{
   m_timer.Dispose();
   GC.SuppressFinalize(this);
}
}

You may look the rule CA1001: http://msdn.microsoft.com/en-us/library/ms182172.aspx

BiwiGreg
  • 66
  • 6