Re: Forms that won't Dispose by Alex
Alex
Tue Dec 16 04:19:59 CST 2003
Hi Andreas,
Thanks for the code. I'll convert it to VB and use it to ensure all
menu-items are properley killed at dispose-time in my app. I think the
solution Ying-Shen has given me will sort it, but knowing that there is an
actual problem in the framework (and where it is, how to override the
behaviour etc) is certainly helpful. Your profiler is an excellent tool by
the way :-)
Cheers,
Alex Clark
"Andreas Suurkuusk" <andreas@onlinescitech.se> wrote in message
news:uQ4ryT0wDHA.2568@TK2MSFTNGP09.phx.gbl...
> Hi,
>
> I did a quick check of this memory leak. Using our profiler it was easy to
> see that the Form is being referenced by a MenuItem that has been added to
> the static Hashtable "allCreatedMenuItems". By disassembling the MenuItem
> class it's also quite easy to see what's causing the MenuItem to be added
to
> the hashtable. When the menuitem is shown AND when it's updated, a
> MENUITEMINFO class is created. When creating the MENUITEMINFO a uniqueId
for
> the MenuItem is assigned (allowing the MenuItem to be identified when
> receiving Win32 messages). The MenuItem is then added to the
> "allCreatedMenuItems" hashtable. In the Dispose function the MenuItem is
> removed from this hashtable. The problem is that the MenuItem is added to
> the hashtable, with a new uniqueId, each time the MenuItem is updated.
>
> The following code can be found in MenuItem.CreateMenuItemInfo:
>
> <snip>
> this.uniqueId = ++MenuItem.createdMenuItemsCounter;
> MenuItem.allCreatedMenuItems.Add( this.uniqueId, this );
> </snip>
>
> If the above code was replaced with the following, there would be no
memory
> leak:
>
> <snip>
> if( uniqueId == -1 ) // uniqueId is assigned to -1 in the constructor.
> {
> this.uniqueId = ++MenuItem.createdMenuItemsCounter;
> MenuItem.allCreatedMenuItems.Add( this.uniqueId, this );
> }
> </snip>
>
> Another sideeffect of this bug is that the allCreatedMenuItems hashtable
> grows each time a MenuItem is changed (e.g. changing the Enable property),
> and never shrinks (except for one entry being removed in
MenuItem.Dispose).
>
> The problem with the growing hashtable has actually been reported as a bug
> previously in this newsgroup.
>
> The only workaround I can come to think of is to use reflection to
explictly
> remove all added entries from the hashtable.
>
> Something like:
>
> static Hashtable allCreatedMenuItems;
> void DisposeMenu( Menu menu )
> {
> if( allCreatedMenuItems == null )
> {
> Type miType = typeof( MenuItem );
> FieldInfo htField = miType.GetField( "allCreatedMenuItems",
> BindingFlags.Static | BindingFlags.NonPublic );
> allCreatedMenuItems = (Hashtable)htField.GetValue( null );
> }
>
> // Find out keys to remove.
> ArrayList keysToRemove = new ArrayList();
> foreach( DictionaryEntry de in allCreatedMenuItems )
> {
> if( de.Value == menu )
> keysToRemove.Add( de.Key );
> }
> // And remove them.
> foreach( object key in keysToRemove )
> {
> allCreatedMenuItems.Remove( key );
> }
>
> // Dispose children
> foreach( MenuItem mi in menu.MenuItems )
> {
> DisposeMenu( mi );
> }
> }
>
> Add the above code to the Dispose method of the Form containing the Menu
> (the Menu and all its MenuItems should already have been Disposed, which
is
> done automatically by the code generated by the forms designer):
>
> /// <summary>
> /// Clean up any resources being used.
> /// </summary>
> protected override void Dispose( bool disposing )
> {
> if( disposing )
> {
> if (components != null)
> {
> components.Dispose();
> }
> DisposeMenu( contextMenu1 );
> }
> base.Dispose( disposing );
> }
>
> I hope this solves your problems and that this message answers your
question
> in the e-mail you sent to us.
>
> Best regards,
>
> Andreas Suurkuusk
> SciTech Software AB
>
>
>
> "Alex Clark" <alex@theclarkhome.spamtin.net> skrev i meddelandet
> news:e6%23tfuvwDHA.3416@tk2msftngp13.phx.gbl...
> > Hi Ying-Shen,
> >
> > I've added a routine called RecursiveDispose(container as Control) which
> > basically goes through all controls on the form and the controls
contained
> > within them, calling dispose on each one recursively. I'm calling this
> from
> > within the Dispose method of the form. By doing this, I can be assured
> that
> > absolutely EVERYTHING is getting Dispose called on it, including the
> > ContextMenu and all of it's menu-items.
> >
> > Next, I've added about 5 images to the form to increase it's memory
> > footprint so that I can see in Task Manager if the memory useage is
going
> up
> > or down.
> >
> > Also, I've altered the code to show Form2 as follows:
> >
> > Dim f As Form2 = New Form2
> > f.ShowDialog()
> > f.Dispose()
> > f = Nothing
> >
> > GC.Collect()
> > GC.WaitForPendingFinalizers()
> > GC.Collect()
> > GC.WaitForPendingFinalizers()
> >
> > Sadly, none of this has cured the problem. Please note that it is ONLY
> when
> > I show the context menu on Form2: If I don't show it, there isn't a
memory
> > leak.
> >
> > I load the application, check the mem useage, click the button to load
> > Form2, the memory has (of course) gone up. I then close Form2, the
> > mem-useage goes down to what it was originally (within a tolerance of a
> few
> > kb). All's well and good.
> >
> > HOWEVER, if I repeat the process but this time click the button on Form2
> to
> > show the Context Menu and then close it, the mem-useage DOES NOT DROP,
it
> > stays pretty much identical to what it was while Form2 was open.
> >
> > I've been using the .NET Memory Profiler by SciTech Software to confirm
> that
> > in these cases, Form2 is not being unloaded from memory. Is there any
way
> I
> > can forcibly kill a Form from memory? Is there any other workaround?
> This
> > is pretty urgent as it's having a detrimental affect on a large project
> > which is due to go live this month, and my customer is getting more and
> more
> > disenchanted with .NET!
> >
> > Cheers,
> > Alex Clark
> >
> >
>
>