.NET Forum / Windows Forms / WinForm General / June 2004
Memory in windows forms
|
|
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
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 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 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 MagazinesGet 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 ...
|
|
|