StatusStrip control leaks its collection items

  • Thread starter Thread starter carl.clawson
  • Start date Start date
C

carl.clawson

The StatusStrip control leaks when you clear its Items collection. You
can try the code below. Yes, this looks like an odd way to use a
progress bar but I distilled this snippet from a moderately large
application that was running out of user handles (10000 max) after
some hours of use because of just this problem. Try it. Just make a
form with a timer, a button, a label, and a StatusStrip. Push the
button and you will see it leak user handles in the task manager.

Workaround: Explicitly dispose each toolstrip item before clearing the
collection. Set DONT_LEAK = True to do this.

Is there a better way to solve the problem? Please tell me.

I found a few articles on MSDN about leaks with StatusStrips but none
of them appear related to this. In particular I have AllowItemReorder
= False, so this is not the problem that was reported to occur when
you have that set True.

Thanks for listening,
Carl

Public Class Form1
Private Const DONT_LEAK As Boolean = False
Private progressCount As Integer
Private progressTarget As Integer = 20000
Private Sub cmdThrashStrip_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles cmdThrashStrip.Click
progressCount = 0
Timer1.Interval = 10
Timer1.Enabled = True
End Sub
Private Sub UpdateProgress()
Try
If DONT_LEAK Then
' Disposing directly from collections gives
"collection modified" exception
' from enumerator
Dim killList As New Generic.List(Of ToolStripItem)
For Each item As ToolStripItem In StatusStrip1.Items
killList.Add(item)
Next
StatusStrip1.Items.Clear()
For Each item As ToolStripItem In killList
item.Dispose()
Next
Else
StatusStrip1.Items.Clear()
End If
Dim pb As New ToolStripProgressBar
pb.Value = 100 * progressCount / progressTarget
StatusStrip1.Items.Add(pb)
If progressCount >= progressTarget Then
Timer1.Enabled = False
StatusStrip1.Items.Add("Done")
End If
Catch ex As Exception
' Ha! No handle left to even put up a message box!
Label1.Text = ex.Message
End Try
End Sub
Private Sub Timer1_Tick(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Timer1.Tick
progressCount += 1
UpdateProgress()
End Sub
End Class
 
I didn't mention the other workaround: instead of adding a new
progress bar, search the collection and re-use the existing one. This
works fine in my little example. However my bigger application still
leaks when I do this, although at a much lower rate. I'll revisit that
in light of my experience with the little snippet.

-- Carl
 
It would seem to make sense that you would have issues if you don't dispose
something that can be disposed.

Why don't you just make a single instance visible or not visible? What I do
is allocate all the controls on the status strip in the designer and use them
in place as needed.
 
It would seem to make sense that you would have issues if you don't dispose
something that can be disposed.

I don't expect to have to dispose things explicitly unless there's a
reason that I need to have it done at a specific point in the code.
That's what the garbage collector is for. In the case of the Status
Strip, removed controls are not getting garbage collected. Any other
control collection I've ever used will release its contents properly
on a Clear( ) or Remove( ), and they then become eligible for garbage
collection if no other references to them exist.

If there's something special about the StatusStrip that I'm not
getting, someone please clue me in. I don't see in its documentation
that any special handling is required for it or its Items collection.
Why don't you just make a single instance visible or not visible?  WhatI do
is allocate all the controls on the status strip in the designer and use them
in place as needed.

Good idea. My example wasn't meant to demonstrate how one SHOULD do
it. It was to demonstrate a problem. The code was silly but it
shouldn't have leaked 10,000 handles all over the floor.

-- Carl
 
I don't expect to have to dispose things explicitly unless there's a
reason that I need to have it done at a specific point in the code.
That's what the garbage collector is for. In the case of the Status
Strip, removed controls are not getting garbage collected. Any other
control collection I've ever used will release its contents properly
on a Clear( ) or Remove( ), and they then become eligible for garbage
collection if no other references to them exist.

If there's something special about the StatusStrip that I'm not
getting, someone please clue me in. I don't see in its documentation
that any special handling is required for it or its Items collection.


Good idea. My example wasn't meant to demonstrate how one SHOULD do
it. It was to demonstrate a problem. The code was silly but it
shouldn't have leaked 10,000 handles all over the floor.

-- Carl

There are several interrelated things going on here.

The StatusStrip controls won't be eligible for garbage collection
until all references to them are gone.

The Windows resources held by those controls will be freed either when
you Dispose them or when the garbage collector frees them.

The garbage collector knows nothing about non-managed resources like
Windows handles. Even if all references to the controls are gone, the
garbage collector won't run until the app needs more memory and that
might not occur before your app runs out of handles.

You should always Dispose any object that uses non-managed resources
when you are finished with it.
 
This is categorically WRONG.

Jack's reply addresses this, but because it's so important, let me be very
clear: if an object implements IDisposable, it's _essential_ that you call
Dispose() on the object when you're done with it. The garbage collector
is for releasing _managed_ memory. _Disposing_ objects is the exact
opposite of "what the garbage collector is for".

Pete

1. Create a UserControl
2. add a ToolStrip to the UserControl
3. add a ToolStripTextBox(or a ToolStripComboBox) to the UserControl
4. Create a form and add the UserControl to the form(remeber as Class
Form2)
5.Create another form(remeber as Class Form1)
6. add a button1 to form1.
7.create button1_Click
private void button1_Click(object sender, EventArgs e)
{
using (Form2 f = new Form2())
{
f.ShowDialog();
}
}

evertime click the button to show Form2,u can see GDI objects+1(Use
Windows Task Manager)

in this case,do i need Dispose this ToolStripTextBox£¿
 
This is categorically WRONG.

Jack's reply addresses this, but because it's so important, let me be very  
clear: if an object implements IDisposable, it's _essential_ that you call  
Dispose() on the object when you're done with it.  The garbage collector  
is for releasing _managed_ memory.  _Disposing_ objects is the exact  
opposite of "what the garbage collector is for".

Pete

Sorry Pete, but I don't agree. Look at the standard Dispose/Finalize
pattern. like here: http://msdn.microsoft.com/en-us/library/s9bwddyx.aspx.
You'll see that when a disposable object is finalized, its unmanaged
resources are released. That's what the Dispose(False) in the
finalizer does. You need to dispose it only if you need to control
WHEN the unmanaged resources get released, or if you need to force
managed resources to be released before they would otherwise get
finalized. A good example is if you've opened a stream on a file and
need to re-open it. If you don't Dispose (or Close) the old stream,
you have to wait an unpredictable amount of time before the file will
open again because you've left a stream hanging around with an open
file handle that won't close until the garbage collector gets to it.

Now, it's certainly a good idea to dispose things when you're done
with them. No argument there. But I don't get why it's absolutely
necessary, and I've never had a problem until I bumped into the
StatusStrip. The finalizer will eventually take care of it as long as
all references to the object are gone. I would say that any finalizer
that fails to do so is buggy. There may be some special cases with
system-wide resources that don't get released on program exit because
I don't think finalizers always get called on exit. But that's not
what we're talking about here.

Theoretical arguments aside, I can do the very same experiment with
any other control, like buttons. Create a bunch, put them on a form,
take them off. Repeat indefinitely. You can watch the handle count
grow and then suddenly drop back repeatedly as the unused buttons get
garbage collected. So...why can I get away with not disposing buttons,
but I can't get away with not disposing ToolStripItems? I tried this
with a form full of buttons just now and the handle count never rises
much above 100 before dropping back down to a dozen or two.

-- Carl
 
Disagree all you want.  The fact remains that by your own admission, if 
you'd followed the .NET requirement of disposing disposable objects, you  
wouldn't have a problem.  That may not be incontrovertible proof, but it's  
sure a heck of a strong suggestion.

Pete

I agree that garbage collection is non-deterministic. No argument
there. I also agree that calling Dispose when possible is a very good
practice. It's what I normally do.

But I still maintain that an object that wraps an unmanaged resource
must release that resource in its finalizer. If the finalizer never
gets called, so be it. The object can't help that.

Now back to the problem at hand, I am finding that controls removed
from the StatusStrip appear to either A) never get finalized, which
means a reference to them is probably being held somewhere because
other deleted controls ARE getting finalized, or B) they are getting
finalized but not releasing their handles. OK, per our discussion,
point "A" is not an absolute proof of a problem, but it's pretty darn
good evidence. I remain convinced that there IS a problem with these
controls!

Thanks for the explanations, Pete.
-- Carl
 
It's not just "very good practice".  It's mandatory for correct code.

Yes. I was trying to make a point and got a bit carried away. The
essential fact that I skated right over, is that the garbage collector
doesn't care about handles or other non managed resource. It will
clean up the memory when it thinks it needs to, and the handles will
get released at that point, if and when that happens.
However, the finalizer doesn't exist for correct code.  It exists for  
incorrect code.  That is, it's a backup plan for dealing with code thatis  
buggy.

Yes. That is a much more clear statement of what I was trying to say
when I said something like "that's what the garbage collector is for."
For the record, I hate nondeterminism and struggle against it
constantly. Anyhow, I'm an old C++ guy, so I'm used to cleaning up
after myself.

Never forget the old NASA saying: "Once you start depending on a
backup system, it's not a backup system any more." Even though I've
found the GC to be pretty darn good at, um, hiding my mistakes from
me, it is not good to depend on it.
In fact, if anything this illustrates why relying on the non-deterministic  
behavior of finalization is such a bad idea.  It pushes other bugs farther  
out, making them harder to find and fix.

Pete

Yep. This was pretty hard to find, all right!

Let's just say that the StatusStrip and its items are for some unknown
reason less well behaved than other Windows Forms controls in this
regard. I am not the only person to have had trouble with them.
Anyhow, I no longer have a problem so I don't plan to goof around with
them any more.

Thanks for making me stop a moment and think clearly.
Carl
 
Back
Top