Reputation: 46
void Foo()
{
System.Windows.Forms.Form f = new System.Windows.Forms.Form();
f.Show();
}
To my understanding f holds the reference to the Form. But f is a local variable and it will go out of scope when the control leaves the curly braces. But the Form is still open. I tried calling GC.Collect(), but the Form is still open.
One more scenario.
private void button2_Click(object sender, EventArgs e)
{
Timer t = new Timer();
t.Enabled = true;
t.Interval = 1000;
t.Tick += new EventHandler(t_Tick);
}
void t_Tick(object sender, EventArgs e)
{
}
In this scenario the t is never getting garbage collected. After a lot of research, I found Timer class is requesting GC not to collect using - GCHandle.Alloc when I set t.Enabled = true. Guys, this is a big source of memory leak. Unless I set t.Enabled = false, the whole Form will be leaked even after we close the Form.
In the first example code, I could not understand why the Form didn't get garbage collected even after I trigger a GC.Collect(). In reflector I saw ControlNativeWindow has been used in Form which internally uses GCHandle.Alloc. Is that a reason?. As a user of the .NET library, I always believed that when a reference goes not reachable, it will get a chance for garbage collection. Of-course garbage collection and the actual release from memory is un-deterministic. But my question here is - Is my understanding correct for both the examples? When there are objects that can live even after it goes not-reachable, then how will I track that to prevent memory leak?
Upvotes: 2
Views: 1771
Reputation: 21321
You have to manage lifetime of Form
and System.Windows.Forms.Timer
instances yourself. You need to call Dispose
to mark end of their lifetime and GC
will collect them afterwards. Closing a form calls Dispose
internally. If your timer was placed on the form using designer then the timer will be disposed when the form is closed. This is achieved by designer generating the following code in the form constructor:
this.components = new System.ComponentModel.Container();
this.timer1 = new System.Windows.Forms.Timer(this.components);
and Dispose
of the form is:
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
So in reality there's no memory leak as long as you close your forms and not just hide them. But if you created your timer manually then you have to dispose it yourself when the form is closed.
On the other hand System.Threading.Timer
will be garbage-collected if you don't have any references to it even if you didn't Dispose
it.
Upvotes: 1
Reputation: 941970
Winforms keeps an internal table that maps handles to control instances. That table ensures that a control (the form in your case) can never be garbage collected as long as the native window is alive. It gets removed from that table when the window is destroyed, either by the user closing the form or your code disposing it.
The System.Timers.Timer is kept alive by a cookie that's referenced by the CLR. The class is implemented with a System.Threading.Timer which has a constructor that takes a state object argument. That state object is the cookie, the CLR keeps it referenced with the equivalent of GCHandle.Alloc(). Disabling the timer resets the cookie which allows the timer to be garbage collected.
These are natural and necessary ways that the framework prevents these objects from getting garbage collected too early. You can only cause a leak by forgetting to disable the timer when the form is disposed. Which in general is quite unhealthy, you don't want a timer to keep ticking when the form is dead. Move the Dispose method from the Designer.cs file into the form code or override OnFormClosed() to disable the timer.
Upvotes: 4