5

I have a .NET Framework Console app (4.6.2 and 4.7.2 exhibit this issue). It opens a SaveFileDialog 5 times in a row, each time waiting for the previous dialog to close.

Looking at the GDI column in TaskManager, I'm seeing the GDI object count start at 79, then go to 83, then 87, etc. It leaks 4 objects each time. This doesn't happen with an OpenFileDialog or other CommonDialogs that I've tested.

I am disposing the object, and I don't see this issue with .NET Core 3.1 (I don't see the issue in my .NET Core 3.1 app, but in the minimal example below, I do see it in both .NET Core 3.1 and in .NET 6).

The GDIView tool shows this:

Handle Object Type Kernel Address Detect Counter
0xbc041a92 Region 0xffffffffffbc1a92 1
0x8410215d Brush 0xffffffffff84215d 1
0x811024af Brush 0xffffffffff8124af 1
0x620433b1 Region 0xffffffffff6233b1 1
0x4a103550 Brush 0xffffffffff4a3550 1
0x8e043569 Region 0xffffffffff8e3569 1
0x35103a54 Brush 0xffffffffff353a54 1
0xda013d30 DC 0xffffffffffda3d30 1
0xdc043fc6 Region 0xffffffffffdc3fc6 1

C# code:

using System;
using System.Threading;
using System.Windows.Forms;

namespace GDILeakTest
{
    internal class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
            {
                using (var dialog = new SaveFileDialog())
                {
                    dialog.ShowDialog();    
                }
                Thread.Sleep(5000);
            }
        }
    }
}

Is this a known bug in .NET?

Update: two things that seems to address the leak (though interestingly enough, these don't help in my actual app).

  1. Adding an Application.Run() to the end of the Main method
  2. Removing [STAThread] and showing the dialog inside of an STA thread:
Thread t = new Thread(() =>
{
    dialog.ShowDialog();
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
t.Join();
pushkin
  • 9,575
  • 15
  • 51
  • 95
  • This is actually interesting. I'm seeing the same leaks even on Core 3.1 and NET 6.0. Which is really what I expected since they all should just use Common Dialog from windows. Note: Win10 v19044 – NPras May 27 '22 at 04:41
  • 1
    _etc_, Well, how often did you let it go? It __may__ simply be a delayed collection, as the collection may cost more than letting the few bytes dangle for a while.. – TaW May 27 '22 at 06:30
  • @NPras Looks like you're right. In my actual .NET Core app, I couldn't repro this leak for some reason. But with my minimal example, I can in .NET Core and .NET 6 – pushkin May 27 '22 at 15:12
  • @TaW I waited over a minute – pushkin May 27 '22 at 15:17
  • 2
    You are not just debugging your own code anymore when you use these shell dialogs. You also get to see what all of the installed shell extension handlers do. They can be buggy, but one of the worst things you can do to them is lying. [STAThread] is a promise, cross your heart, hope to die. Promise is to never block and to have a dispatcher loop. You don't have one. Death involves code you didn't write and can't see; it is apt to deadlock. And of course, when code deadlocks then hoping for proper cleanup is but idle hope. https://stackoverflow.com/a/21684059/17034 – Hans Passant May 27 '22 at 15:32
  • _I waited over a minute_ Time in't of the essence. When the system sees no urgent need to clean, up a mintute or any other amount of time shouldn't make a difference.. – TaW May 27 '22 at 17:00
  • @HansPassant Looking at that post gave me an idea. It seems like adding an `Application.Run()` call at the end of the main method fixes the leak (though I don't totally get how because that code only runs at end of the main method because I'm seeing no leak earlier when the dialogs are being shown). I don't know enough about this area to post a meaningful answer, but if you could, that would be appreciated (unless this is a dupe of something) [though my actual app that exhibits this calls `Application.Run()` and there's still a leak, so there's something else going on in that case] – pushkin May 27 '22 at 17:16

0 Answers0