Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / Windows Forms / WinForm General / June 2004

Tip: Looking for answers? Try searching our database.

Memory in windows forms

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
Marina - 01 Jun 2004 20:23 GMT
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
- 02 Jun 2004 20:29 GMT
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

> Hi,
> Consider the following situation
[quoted text clipped - 37 lines]
>
> Thanks
Marina - 02 Jun 2004 21:17 GMT
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
[quoted text clipped - 48 lines]
> >
> > Thanks
Cor Ligthert - 03 Jun 2004 07:42 GMT
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
Marina - 03 Jun 2004 13:49 GMT
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?

> Hi Marina,
>
[quoted text clipped - 13 lines]
>
> Cor
Cor Ligthert - 03 Jun 2004 15:03 GMT
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
Jon Skeet [C# MVP] - 03 Jun 2004 15:12 GMT
> 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.
[quoted text clipped - 6 lines]
> 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.)

Signature

Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
If replying to the group, please do not mail me too

Marina - 03 Jun 2004 18:33 GMT
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

> > 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.
[quoted text clipped - 17 lines]
> to paste your code into an empty text file and compile it from the
> command line.)
Armin Zingler - 03 Jun 2004 19:26 GMT
> Private Sub Button1_Click(ByVal sender As System.Object, ByVal e
> As System.EventArgs) Handles Button1.Click
[quoted text clipped - 3 lines]
> curControl = New UserControl1
> End Sub

> Private Sub Button2_Click(ByVal sender As System.Object, ByVal e
> As System.EventArgs) Handles Button2.Click
[quoted text clipped - 5 lines]
> 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.

Signature

Armin

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

Cor Ligthert - 03 Jun 2004 20:15 GMT
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
Cor Ligthert - 03 Jun 2004 19:48 GMT
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
////
Marina - 04 Jun 2004 14:37 GMT
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.

> Hi Marina/Jon,
>
[quoted text clipped - 39 lines]
>     End Sub
> ////
Cor Ligthert - 04 Jun 2004 15:32 GMT
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
Marina - 04 Jun 2004 15:46 GMT
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.

> Marina,
>
[quoted text clipped - 7 lines]
>
> Cor
- 04 Jun 2004 16:00 GMT
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

> 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
[quoted text clipped - 15 lines]
> >
> > Cor
Cor Ligthert - 04 Jun 2004 16:39 GMT
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

> 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
[quoted text clipped - 3 lines]
> 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.
Jon Skeet [C# MVP] - 08 Jun 2004 15:47 GMT
> 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...

Signature

Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
If replying to the group, please do not mail me too

Cor Ligthert - 08 Jun 2004 16:47 GMT
Just to Ack, as seen

Cor
Jon Skeet [C# MVP] - 10 Jun 2004 10:41 GMT
> 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);
       }
   }
}

Signature

Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
If replying to the group, please do not mail me too

Cor Ligthert - 10 Jun 2004 11:18 GMT
Hi John,

That shows the same as my testing, however I advice you to do a test with
visible of the controls is set to not true as well, otherwise you will maybe
think it is the add, while it is in my expirience not, however the paint.

Cor
Jon Skeet [C# MVP] - 10 Jun 2004 11:45 GMT
> That shows the same as my testing, however I advice you to do a test with
> visible of the controls is set to not true as well, otherwise you will maybe
> think it is the add, while it is in my expirience not, however the paint.

Well, making it visible or not may well have far more involved than
just whether or not it ends up being painted. I'm not terribly worried
about that level of detail - the main thing (to my mind, anyway) is why
the behaviour is different, and the key thing is that when added and
visible, garbage is being generated earlier, forcing more frequent
garbage collections.

Signature

Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
If replying to the group, please do not mail me too

Cor Ligthert - 10 Jun 2004 12:57 GMT
Hi Jon,

I can think of one thing, therefore I said painting, while the screen is
painted there is as far as I know some time, that the video processor is
busy, it would be clever to do than some garbage collection when there are
no other processes busy.

Cor

> > That shows the same as my testing, however I advice you to do a test with
> > visible of the controls is set to not true as well, otherwise you will maybe
[quoted text clipped - 6 lines]
> visible, garbage is being generated earlier, forcing more frequent
> garbage collections.
Jon Skeet [C# MVP] - 03 Jun 2004 20:27 GMT
> Ok, here is a complete program.  First file is the form, second is the
> UserControl in question.
[quoted text clipped - 5 lines]
> 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...

Signature

Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
If replying to the group, please do not mail me too


Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.