Memory in windows forms

M

Marina

Hi,
Consider the following situation
I have the following routine running repeatedly (curControl is a UserControl
with say 1000 textboxes and a big array of strings):

Public Sub AddControl(ByVal ctlName As String)
If Not IsNothing(curControl) Then
Me.Controls.Remove(curControl)
End If

Dim assemb As [Assembly] = [Assembly].Load("MyControlAssembly")
curControl = CType(assemb.CreateInstance(ctlName), UserControl)
curControl.Location = New Point(150, 20)
Me.Controls.Add(curControl)
End Sub

Now, running this Sub over and over, the memory stays more or less the same.
That is to say, it goes up a litte bit - maybe 4-20K, but nothing huge.

Now, consider this version:

Public Sub AddControl(ByVal ctlName As String)
If Not IsNothing(curControl) Then
curControl.Dispose()
End If

Dim assemb As [Assembly] = [Assembly].Load("MyControlAssembly")
curControl = CType(assemb.CreateInstance(ctlName), UserControl)
curControl.Location = New Point(150, 20)
End Sub

Now, in this version, the control is not being added to the form - it is
just created.

In this case, the memory goes up by about .5 MB every time this code runs.

Now, the questions is, why is it that in the first scenario, the control is
cleaned up properly - but in the second scenario (where Dispose is actually
being closed), it is not. The memory keeps climbing up and up.

Thanks
 
G

Guest

In the second example, you're calling dispose, but you're not removing the
control, so a reference to it remains. You should call
Me.Controls.Remove(curControl) then curControl.Dispose() in both case. It's
not an either/or situation.

Pete
 
M

Marina

I apologize for the misprint. That line that adds the control is actually
commented out in my experiment for test #2.

In the second example, you're calling dispose, but you're not removing the
control, so a reference to it remains. You should call
Me.Controls.Remove(curControl) then curControl.Dispose() in both case. It's
not an either/or situation.

Pete

Marina said:
Hi,
Consider the following situation
I have the following routine running repeatedly (curControl is a UserControl
with say 1000 textboxes and a big array of strings):

Public Sub AddControl(ByVal ctlName As String)
If Not IsNothing(curControl) Then
Me.Controls.Remove(curControl)
End If

Dim assemb As [Assembly] = [Assembly].Load("MyControlAssembly")
curControl = CType(assemb.CreateInstance(ctlName), UserControl)
curControl.Location = New Point(150, 20)
Me.Controls.Add(curControl)
End Sub

Now, running this Sub over and over, the memory stays more or less the same.
That is to say, it goes up a litte bit - maybe 4-20K, but nothing huge.

Now, consider this version:

Public Sub AddControl(ByVal ctlName As String)
If Not IsNothing(curControl) Then
curControl.Dispose()
End If

Dim assemb As [Assembly] = [Assembly].Load("MyControlAssembly")
curControl = CType(assemb.CreateInstance(ctlName), UserControl)
curControl.Location = New Point(150, 20)
End Sub

Now, in this version, the control is not being added to the form - it is
just created.

In this case, the memory goes up by about .5 MB every time this code runs.

Now, the questions is, why is it that in the first scenario, the control is
cleaned up properly - but in the second scenario (where Dispose is actually
being closed), it is not. The memory keeps climbing up and up.

Thanks
 
C

Cor Ligthert

Hi Marina,

You got no further answers I see, my thought was that the dispose is maybe
not well done in your usercontrol. However just a gues and therefore such a
late answer.

In your example 1 you set a reference to the control and remove that
reference
In your example 2 you set no reference to the control so there is nothing to
remove

So example 2 should work the same as example 1 even without the dispose.
That would mean in my opinion that in the dispose a reference is set.

However just guessing.

Cor
 
M

Marina

In the first example, I do not call Dispose at all. I just add the control
so it is visible - and then remove it.
However, the control is cleaned up properly, and its memory released.

In the second version, I tried doing everything the same exact the
add/remove. The memory was now not released properly.

I then tried adding the Dispose, just in case. This didn't help.

The questions is: why is adding/removing the control to the form, somehow
clean up its memory when the control is no longer referenced by anything.
But just creating it, and dereferencing it, does not?
 
C

Cor Ligthert

Hi Mariane,

I thought that you would have done as you said and that that was your start,
in my opinion is this a typical question for Jon.

It seems as you said that the add and/or the remove are adding/removing
references more than only the reference to the parent.

However why would there be a reference.

I do not like this kind of problems anymore, it is following deep the code
and than reading somewhere else suddenly why this behaviour is.

Cor
 
J

Jon Skeet [C# MVP]

Cor Ligthert said:
I thought that you would have done as you said and that that was your start,
in my opinion is this a typical question for Jon.

It seems as you said that the add and/or the remove are adding/removing
references more than only the reference to the parent.

However why would there be a reference.

I do not like this kind of problems anymore, it is following deep the code
and than reading somewhere else suddenly why this behaviour is.

Okay, I'll have a look.

Marina, could you come up with a short but complete example which
demonstrates the problem?

See http://www.pobox.com/~skeet/csharp/complete.html

(Don't worry, you don't need to write it in C# - I just want to be able
to paste your code into an empty text file and compile it from the
command line.)
 
M

Marina

Ok, here is a complete program. First file is the form, second is the
UserControl in question.

Run it and click button1 repeatedly. Memory will keep growing and growing
with every click (500K at a time or so).

Stop it, and run it again. This time click button2 repeatedly. Memory will
grow at first, but remain steady - growing maybe 4-20K at a time - so barely
noticeable.

Thanks for your interest...

Form:

Public Class Form1
Inherits System.Windows.Forms.Form
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Friend WithEvents Button1 As System.Windows.Forms.Button
Friend WithEvents Button2 As System.Windows.Forms.Button
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Me.Button1 = New System.Windows.Forms.Button
Me.Button2 = New System.Windows.Forms.Button
Me.SuspendLayout()
'
'Button1
'
Me.Button1.Location = New System.Drawing.Point(24, 24)
Me.Button1.Name = "Button1"
Me.Button1.TabIndex = 0
Me.Button1.Text = "Button1"
'
'Button2
'
Me.Button2.Location = New System.Drawing.Point(24, 64)
Me.Button2.Name = "Button2"
Me.Button2.TabIndex = 1
Me.Button2.Text = "Button2"
'
'Form1
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(552, 598)
Me.Controls.Add(Me.Button2)
Me.Controls.Add(Me.Button1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
End Sub
#End Region
Dim curControl As UserControl
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
If Not IsNothing(curControl) Then
curControl.Dispose()
End If
curControl = New UserControl1
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button2.Click
If Not IsNothing(curControl) Then
Controls.Remove(curControl)
End If
curControl = New UserControl1
curControl.Location = New Point(50, 50)
Controls.Add(curControl)
End Sub
End Class

UserControl:

Public Class UserControl1
Inherits System.Windows.Forms.UserControl
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
init()
End Sub
'UserControl1 overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
'
'UserControl1
'
Me.Name = "UserControl1"
Me.Size = New System.Drawing.Size(392, 424)
End Sub
#End Region
Dim myData As New ArrayList
Public Sub init()
For i As Integer = 1 To 1000
Dim t As TextBox = New TextBox
t.Location = New Point(0, i * 40)
t.Text = i & "test"
Me.Controls.Add(t)
myData.Add(i & "test")
Next
End Sub
End Class
 
A

Armin Zingler

Marina said:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e
As System.EventArgs) Handles Button1.Click
If Not IsNothing(curControl) Then
curControl.Dispose()
End If
curControl = New UserControl1
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e
As System.EventArgs) Handles Button2.Click
If Not IsNothing(curControl) Then
Controls.Remove(curControl)
End If
curControl = New UserControl1
curControl.Location = New Point(50, 50)
Controls.Add(curControl)
End Sub


Maybe it's too simple, but I think the solution is that clicking Button2
uses much more memory because the control is added to the Form. This causes
the garbage collection and memory to be freed. With Button1, you also need
a lot of memory, but not as much as when making the Usercontrol including
1000 textboxes visible as done in Button2_click, so Button1_click consumes
much less memory and consequently GC isn't started.
Simply leave out curControl.dispose in Button1_click and you will see that
memory is freed earlier. I also suggest to add this to the Usercontrol:

Protected Overrides Sub Finalize()
Debug.WriteLine("finalize " & Date.Now.TimeOfDay.ToString)
MyBase.Finalize()
End Sub

In Sub New:
Debug.WriteLine("Sub New " & Date.Now.TimeOfDay.ToString)

In Sub Dispose:
Debug.WriteLine("Dispose " & Date.Now.TimeOfDay.ToString)

Now, without curControl.dispose in Button1_click, pressing Button1 consumes
memory faster and you will see that GC will take place after few clicks
(after 6 here at my machine). Try this also including curControl.dispose and
you'll see the difference.


In other words:
When calling dispose, less memory is consumed than when not calling dispose.
Consequently GC takes place later and memory is freed later.
When not calling dispose, memory is consumed faster and GC takes place
earlier. Consequently memory is freed earlier.

I'm not 100% sure but I think this can be the explanation.


--
Armin

How to quote and why:
http://www.plig.net/nnq/nquote.html
http://www.netmeister.org/news/learn2quote.html
 
C

Cor Ligthert

Hi Marina/Jon,

Can you try this also,

With me it was not the add handler that triggered the cleaning up of the
memory however it seems to me the changing of the form, (or how you want to
say that).

(I added that dispose in button2 proc because otherwise the program went
completly down).

With visible is true the behaviour as described by Marina the same by me,
with visible = false seems the behaviour for Button1 and Button2 the same.
(I used the taskmanager and nothing more).

However maybe I thougth it alone.

Cor


\\\\
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
For i As Integer = 0 To 100
If Not IsNothing(curControl) Then
curControl.Dispose()
End If
curControl = New UserControl1
Next
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button2.Click
For i As Integer = 0 To 100
If Not IsNothing(curControl) Then
Controls.Remove(curControl)
curControl.Dispose()
End If
curControl = New UserControl1
curControl.Location = New Point(50, 50)
curControl.Visible = False 'this to change
Controls.Add(curControl)
Next
End Sub
////
 
C

Cor Ligthert

HI Armin,
Maybe it's too simple, but I think the solution is that clicking Button2
uses much more memory because the control is added to the Form. This
causes

I thought that the problem was that Button 2 uses less memory

Cor
 
J

Jon Skeet [C# MVP]

Marina said:
Ok, here is a complete program. First file is the form, second is the
UserControl in question.

Run it and click button1 repeatedly. Memory will keep growing and growing
with every click (500K at a time or so).

Stop it, and run it again. This time click button2 repeatedly. Memory will
grow at first, but remain steady - growing maybe 4-20K at a time - so barely
noticeable.

Hmm. (I also notice the second one trailing off completely over time.
My guess is that gen 0 is growing for a while as there are lots of
collections, then deemed okay.)

I suspect that it's creating controls without ever adding them to
anything which is "realised" which is the problem - but I wouldn't like
to say for sure. I'll do some testing myself and see what happens...
 
M

Marina

I find it very interesting that setting Visible to False, does indeed make
the memory for Button2 grow just as Button1 - I was able to reproduce this.

It doesn't seem right that this alone seems to control whether or not memory
for the control is reclaimed.
 
C

Cor Ligthert

Marina,

I have done some more testing.
With not visible it is aprox. 4 times faster than with visible.
When I put the GC.collect between it, the memory stays constant and the
performance loss was 1%

I used the taskmanager for the mem because otherwise the me.refresh is
needed and that was what I did not want to do.

Cor
 
M

Marina

But isn't the idea that the GC should be kicking in on its own? Why is it
kicking in when the control is Visible on its own, but not when its not
Visible?

If a program is doing a lot of work of creating/destroying objects - should
it have a time that calls the GC every now and then because the GC won't
always do the clean up on its own? That doesnt' sound right.
 
G

Guest

Whether or not it's "right," I've found a number of times when I've needed
to give the garbage collector a little push. particularly for processor
intensive operations where lots of memory is being allocated and removed.

As an example, I wrote a stock analysis program a while back that would
analyze a few thousand stocks. It was very processor intensive and the
garbage collector didn't see fit I guess, to free up the memory at any point
during the processing, so periodically I'd force the collection. Otherwise
the app would get very slow due to page faults.

This may be a similar situation. I don't really know the specifics of how
the garbage collector decides when to collect, so it seems that you just
need to do testing and make a judgement call.

Pete
 
C

Cor Ligthert

Hi Marina,

I did not say this to use (this is an issolated problem), this is more to
show that strange behaviour, Jon said he would do some testing as well
therefore I tell here what I find. However, also the things I find strange.
I readed that document about the GC again and could find nothing find that
told that it was affected by the painting of a screen and therefore I tested
it.

I somehow just get the idea (I do not know why) that some GC is done in
screenpainting time.

Just guessing of course.

Cor
 
J

Jon Skeet [C# MVP]

Cor Ligthert said:
I did not say this to use (this is an issolated problem), this is more to
show that strange behaviour, Jon said he would do some testing as well
therefore I tell here what I find. However, also the things I find strange.
I readed that document about the GC again and could find nothing find that
told that it was affected by the painting of a screen and therefore I tested
it.

Just to "ping" this thread and let you both know it's still on my radar
- it's just things have been really busy lately, so I haven't been able
to do any testing. I'll let you know when I've done it though.
I somehow just get the idea (I do not know why) that some GC is done in
screenpainting time.

It shouldn't be...
 
J

Jon Skeet [C# MVP]

Jon Skeet said:
Just to "ping" this thread and let you both know it's still on my radar
- it's just things have been really busy lately, so I haven't been able
to do any testing. I'll let you know when I've done it though.

Okay. First things first: I wrote a C# version to help me test it. I've
included the code below. I removed the ArrayList from the equation,
because I don't think it's particularly relevant.


It's definitely not actually *leaking* memory - although the top for
the "no add" case is higher than the top for the "add" case, they both
have definite tops beyond which they don't go (for long, anyway).


I'm pretty sure it's to do with when garbage collection takes place.
Adding a control will take a lot of memory in this case, so garbage
collection occurs fairly early. When you *don't* add the control,
you've got to wait a lot longer before garbage collection occurs, and
that may well be affecting whether gen0 itself grows or not.

If you run the performance monitor while running the test, and add a
counter for the number of gen0 collections, you'll see you have to hit
the "No add" button several times before there's a collection. If you
hit the "Add" button, there's a collection every time.

So, while you have a slightly higher total memory use when you don't
add, you get less memory churn and fewer garbage collections, which is
basically good.



using System;
using System.Collections;
using System.Drawing;
using System.Windows.Forms;

public class Test : Form
{
UserControl control;

static void Main()
{
Application.Run(new Test());
}

Test()
{
Size = new Size (400, 600);
Location = new Point (200, 200);

Button button = new Button();
button.Text = "No add";
button.Size = new Size (200, 20);
button.Location = new Point (10, 30);
button.Click += new EventHandler (CreateNoAdd);
Controls.Add(button);

button = new Button();
button.Text = "Create and add";
button.Size = new Size (200, 20);
button.Location = new Point (10, 60);
button.Click += new EventHandler (CreateAndAdd);
Controls.Add(button);
}

void CreateNoAdd (object sender, EventArgs e)
{
if (control != null)
{
control.Dispose();
}
control = new UserControl();
}

void CreateAndAdd (object sender, EventArgs e)
{
if (control != null)
{
Controls.Remove(control);
}
control = new UserControl();
control.Location = new Point (10, 90);
Controls.Add(control);
}
}

public class UserControl : Control
{
public UserControl()
{
Size = new Size (400, 400);
for (int i=0; i < 1000; i++)
{
TextBox t = new TextBox();
t.Size = new Size(50, 20);
t.Location = new Point (0, i*40);
t.Text = String.Format ("{0}test", i);
Controls.Add(t);
}
}
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top