|
dev
newsgroups
|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
Garbage collection working sometimes and not others...?discovered something that is causing me a bit of a problem: please take a look at the following test code: public void UnloadAnyControlTest() { Form form = new Form(); Panel panel = new Panel(); WeakReference panelRef = new WeakReference(panel); panel.Dock = DockStyle.Fill; // Below are the two lines of code which, if commented out, // will permit this test to pass. Otherwise, it fails. form.Controls.Add(panel); form.Controls.Remove(panel); GC.Collect(); GC.WaitForPendingFinalizers(); Assert.IsNotNull(panelRef.Target); panel = null; GC.Collect(); GC.WaitForPendingFinalizers(); Assert.IsNull(panelRef.Target); } Basically, I have some user controls that I developed and I was writing some test code to ensure that they were properly cleaned up when I unloaded them. I found that it wasn't happening, and I wrote the above test to see if I could figure out what was going on. Basically, if I create a panel and create a weak reference to it, then null out (destroy) the only other reference I have to the panel, the garbage collector will clean up the panel. If, however, in between there I add the panel to a form and then remove it again, the panel will not be cleaned up! Does anyone know why? Can anyone suggest a solution or shed some light on this issue? Thanks in advance, -John See comment inline.
Show quote "Hassiah" <Hass***@discussions.microsoft.com> wrote in message You remove the panel object from the form's Controls collection, but the news:B3395A22-D9DF-4CAE-BE06-86DC169AA8BF@microsoft.com... > Hi, I am using Team Foundation Server's unit testing features and I have > discovered something that is causing me a bit of a problem: please take a > look at the following test code: > > public void UnloadAnyControlTest() > { > Form form = new Form(); > > Panel panel = new Panel(); > > WeakReference panelRef = new WeakReference(panel); > > panel.Dock = DockStyle.Fill; > > // Below are the two lines of code which, if commented out, > // will permit this test to pass. Otherwise, it fails. > form.Controls.Add(panel); > form.Controls.Remove(panel); > Panel object is still alive and have a reference (variable "panel") pointing to it. So, it cannot be GCed. Thus following call to GC.Collec() does nothing to the Panel object. > GC.Collect(); Now, since the variable "panel" is pointing to null, i.e. it no longer > GC.WaitForPendingFinalizers(); > > Assert.IsNotNull(panelRef.Target); > > panel = null; points to the Panel object instance, the Panel object instance is now eligible for garbage collecting, if there is no other reference pointing to it (in your case, there is no). You do not have to call GC.Collect() to clean up here. GC will clean it up in an supposed optimized manner. Unless the panel takes huge resources (memory) and you want to release them for immediately following memory-hungary process. Show quote > > GC.Collect(); > GC.WaitForPendingFinalizers(); > > Assert.IsNull(panelRef.Target); > } > > Basically, I have some user controls that I developed and I was writing > some > test code to ensure that they were properly cleaned up when I unloaded > them. > I found that it wasn't happening, and I wrote the above test to see if I > could figure out what was going on. > > Basically, if I create a panel and create a weak reference to it, then > null > out (destroy) the only other reference I have to the panel, the garbage > collector will clean up the panel. If, however, in between there I add > the > panel to a form and then remove it again, the panel will not be cleaned > up! > > Does anyone know why? > > Can anyone suggest a solution or shed some light on this issue? > > Thanks in advance, > > -John > Norman Yuan wrote:
> See comment inline. You misinterpreted the OPs code, see inline.Show quote > ....which is exactly what the OP was expecting. Note Assert.IsNotNull in the > > "Hassiah" <Hass***@discussions.microsoft.com> wrote in message > news:B3395A22-D9DF-4CAE-BE06-86DC169AA8BF@microsoft.com... >> Hi, I am using Team Foundation Server's unit testing features and I >> have discovered something that is causing me a bit of a problem: >> please take a look at the following test code: >> >> public void UnloadAnyControlTest() >> { >> Form form = new Form(); >> >> Panel panel = new Panel(); >> >> WeakReference panelRef = new WeakReference(panel); >> >> panel.Dock = DockStyle.Fill; >> >> // Below are the two lines of code which, if commented >> out, // will permit this test to pass. Otherwise, it >> fails. form.Controls.Add(panel); >> form.Controls.Remove(panel); >> > > You remove the panel object from the form's Controls collection, but > the Panel object is still alive and have a reference (variable > "panel") pointing to it. So, it cannot be GCed. Thus following call > to GC.Collec() does nothing to the Panel object. next lines of code. > .... which is also what the OP was expecting. Note the Assert.IsNull in the >> GC.Collect(); >> GC.WaitForPendingFinalizers(); >> >> Assert.IsNotNull(panelRef.Target); >> >> panel = null; > > Now, since the variable "panel" is pointing to null, i.e. it no longer > points to the Panel object instance, the Panel object instance is now > eligible for garbage collecting, if there is no other reference > pointing to it (in your case, there is no). next lines of code. You do not have to call > GC.Collect() to clean up here. GC will clean it up in an supposed The whole point of the exercise was to construct a scenario in which an > optimized manner. Unless the panel takes huge resources (memory) and > you want to release them for immediately following memory-hungary > process. object _should_ be collected to test that the object was disposed properly. -cd Hassiah wrote:
Show quote > Hi, I am using Team Foundation Server's unit testing features and I The problem is simple, I believe: GC.Collect is not guaranteed to do > have discovered something that is causing me a bit of a problem: > please take a look at the following test code: > > public void UnloadAnyControlTest() > { > Form form = new Form(); > > Panel panel = new Panel(); > > WeakReference panelRef = new WeakReference(panel); > > panel.Dock = DockStyle.Fill; > > // Below are the two lines of code which, if commented out, > // will permit this test to pass. Otherwise, it fails. > form.Controls.Add(panel); > form.Controls.Remove(panel); > > GC.Collect(); > GC.WaitForPendingFinalizers(); > > Assert.IsNotNull(panelRef.Target); > > panel = null; > > GC.Collect(); > GC.WaitForPendingFinalizers(); > > Assert.IsNull(panelRef.Target); > } > > Basically, I have some user controls that I developed and I was > writing some test code to ensure that they were properly cleaned up > when I unloaded them. I found that it wasn't happening, and I wrote > the above test to see if I could figure out what was going on. > > Basically, if I create a panel and create a weak reference to it, > then null out (destroy) the only other reference I have to the panel, > the garbage collector will clean up the panel. If, however, in > between there I add the panel to a form and then remove it again, the > panel will not be cleaned up! > > Does anyone know why? anything at all. Basically, you're saying "GC, it's OK to collect now". The GC is free to ignore your request for any reason whatsoever. Adding and removing the panel to the form probably just generated enough additional Gen0 garbage to actually trigger a collect. > To test your control's clean up, forcibly call it's Dispose method and > Can anyone suggest a solution or shed some light on this issue? verify that the right things happen. Beyond that, you have to rely on the runtime to (eventually) clean up your control in real use. If your control is holding an expensive resource, you may need to provide a means to deterministically release that resource. Typically, however, that's not necessary if you've followed proper IDisposable hygiene everywhere (in particular, Dispose of your forms when you're done with them - that will, in turn, Dispose all of the controls on the form). -cd Hi Carl,
Thanks for your reply. It was helpful... let me, however, give you a bit more background. 1. I do not plan on ever calling GC.Collect in my code. 2. I am writing a large WinForms app, and the main form will have 5 or 6 different "screens" or "modes" that it can be in. I plan on writing each "screen" as a user control that will contain sub-controls, and so on. When another screen/mode is selected, I want to unload the first screen and load the second screen into the main form. I want to be sure that the runtime is prepared to free the memory and resources used by the first screen so that my app doesn't suck up a ton of memory. That was the reason for my test. 3. I understand that GC.Collect does not necessarily do anything (although I do believe the documentation says that it "forces garbage collection."). However, I find it very curious that every single time I run the test without adding the panel to a form, it gets cleaned, and every time I run it with adding the panel to a form, it does NOT get cleaned. I spent some time using a tool called Reflector to try to see what happens when a control is added to a form, but apart from MDIControls, I could not find anywhere where a reference gets created from the parent form to the child control (panel in this case). So, I am not fully satisfied yet... but thanks for the information you gave; I did find it insightful and helpful. -John Show quote "Carl Daniel [VC++ MVP]" wrote: > Hassiah wrote: > > Hi, I am using Team Foundation Server's unit testing features and I > > have discovered something that is causing me a bit of a problem: > > please take a look at the following test code: > > > > public void UnloadAnyControlTest() > > { > > Form form = new Form(); > > > > Panel panel = new Panel(); > > > > WeakReference panelRef = new WeakReference(panel); > > > > panel.Dock = DockStyle.Fill; > > > > // Below are the two lines of code which, if commented out, > > // will permit this test to pass. Otherwise, it fails. > > form.Controls.Add(panel); > > form.Controls.Remove(panel); > > > > GC.Collect(); > > GC.WaitForPendingFinalizers(); > > > > Assert.IsNotNull(panelRef.Target); > > > > panel = null; > > > > GC.Collect(); > > GC.WaitForPendingFinalizers(); > > > > Assert.IsNull(panelRef.Target); > > } > > > > Basically, I have some user controls that I developed and I was > > writing some test code to ensure that they were properly cleaned up > > when I unloaded them. I found that it wasn't happening, and I wrote > > the above test to see if I could figure out what was going on. > > > > Basically, if I create a panel and create a weak reference to it, > > then null out (destroy) the only other reference I have to the panel, > > the garbage collector will clean up the panel. If, however, in > > between there I add the panel to a form and then remove it again, the > > panel will not be cleaned up! > > > > Does anyone know why? > > The problem is simple, I believe: GC.Collect is not guaranteed to do > anything at all. Basically, you're saying "GC, it's OK to collect now". > The GC is free to ignore your request for any reason whatsoever. > > Adding and removing the panel to the form probably just generated enough > additional Gen0 garbage to actually trigger a collect. > > > > > Can anyone suggest a solution or shed some light on this issue? > > To test your control's clean up, forcibly call it's Dispose method and > verify that the right things happen. Beyond that, you have to rely on the > runtime to (eventually) clean up your control in real use. > > If your control is holding an expensive resource, you may need to provide a > means to deterministically release that resource. Typically, however, > that's not necessary if you've followed proper IDisposable hygiene > everywhere (in particular, Dispose of your forms when you're done with > them - that will, in turn, Dispose all of the controls on the form). > > -cd > > > >Does anyone know why? Winforms itself holds references to forms and controls as long as theunderlying HWND is valid. Does the test pass if you call panel.Dispose() after it has been removed from the controls collection? Mattias -- Mattias Sjögren [C# MVP] mattias @ mvps.org http://www.msjogren.net/dotnet/ | http://www.dotnetinterop.com Please reply only to the newsgroup. I had already tried that... I tried it again just for fun... right after the
call to: form.Controls.Add(panel); form.Controls.Remove(panel); I put in a call to: panel.Dispose(); Again, as before, the call to Assert.IsNotNull passes but the call to Assert.IsNull (at the end) fails. This is strange to me, but I invite others to try it themselves. It is a bit disconcerting to me that windows will not clean up an object that I no longer have any use for... Show quote "Mattias Sjögren" wrote: > > >Does anyone know why? > > Winforms itself holds references to forms and controls as long as the > underlying HWND is valid. > > Does the test pass if you call panel.Dispose() after it has been > removed from the controls collection? > > > > Mattias > > -- > Mattias Sjögren [C# MVP] mattias @ mvps.org > http://www.msjogren.net/dotnet/ | http://www.dotnetinterop.com > Please reply only to the newsgroup. > All-
I had also posted my perplexing question on the framework.windowsforms group. For a while, I got no replies over there, but this morning a wonderful person had psoted this (below)... I think it is the solution. If I try it and it doesn't work, I will advise, but otherwise I think this is the key. Thanks everyone... thought you might like to know. Solution from Stoitcho Goutsev (100)----------------------- John, This problem (that now I'm confident to say it's a bug in WindowsForms) bugged me since I saw your post. I was running your sample and I was getting the same wrong results over and over again and couldn't figure out why. I spent hours digging with the reflector in the windows forms code and everything looked OK; there was no leaking references to the panel, so it shouldn't have happened. Finally I created my own test application and all of a sudden it was working coorectly. Then I compared yours sample with mine and I found that the difference was that I didn't set the panel's Dock property. That was it! If the panel is docked when removed, something keeps reference to it; if is not docked it gets GC-ed correctly. Knowing where to look at, I finaly found the reason. The layout engine caches in each controls the bounds of all docked child controls. The thing is that it does that using the control as a key in a dictionary. The problem is that when you remove the docked panel nothing clears the cache and the cotrol is still referenced as a key in the dictionary. There are two workarounds that I found: Workaround 1: Before removing the panel form the form set the panel's Dock to None and then remove it. Workaround2: I found that the cache is cleared during layout, thus the solution is to call form's PerformLayout method after removing the panel. In your sample code add: form.PerformLayout() right after the line : panel = null; Sometimes the layout is triggered by itself e.g. if there are other docked controls, but in your case there is none. -- HTH Stoitcho Goutsev (100) |
|||||||||||||||||||||||