16

I've got a very simple form with a TOpenDialog and a button on it. When I press the button, it calls Execute on the dialog. If I watch in the debugger, the act of opening the dialog box spawns something like 14 threads, and they don't go away when I close the dialog box either.

Anyone have any idea what's going on with that?

Mason Wheeler
  • 82,511
  • 50
  • 270
  • 477
  • 3
    Only 14? It feels like an entire operating system is booting... Personally, I have never understood why it takes *so* long to display a file dialog. – Andreas Rejbrand Sep 10 '11 at 17:45
  • 1
    And +1, for you made me learn something new. This remarkable delay *only* appears when you run an app through the debugger. – Andreas Rejbrand Sep 10 '11 at 17:48
  • 14
    I remember reading that the common file dialogs end up loading all the shell extensions so those DLLs could be doing god knows what. – Luke Sep 10 '11 at 18:11
  • @Luke: OK, that makes sense. Do you remember reading about any way to unload them afterwards? – Mason Wheeler Sep 10 '11 at 18:22
  • @Mason: My guess is that you don't need to care about them at all. After all, neither Microsoft nor Embarcadero are stupid... And, as I just learned, the terrible delay only appears when you run your program through the debugger. Do you have any particular reason why this is an issue for you? – Andreas Rejbrand Sep 10 '11 at 18:27
  • @Andreas: Because eventually some of those threads terminate, and when they do it makes my sound pop, which is very annoying. – Mason Wheeler Sep 10 '11 at 18:36
  • The shell spins up threads to enumerate the folders. If you have 14 new threads you should remove some shell extensions. – David Heffernan Sep 10 '11 at 18:37
  • Hm... I get 23 new threads. And I very seldom install things. – Andreas Rejbrand Sep 10 '11 at 18:43
  • @Mason: if that popping sound is somewhat predictable, you could try finding the offending thread using [Procmon](http://technet.microsoft.com/en-us/sysinternals/bb896645). Look what threadid's get loaded, wait for the sound to occur and search for any .wav in the output. (By default, the threadid's are not visible in procmon's output, you'll have to add them). If you find the offending thread, you can start making some assumptions as to what it is and why it is occuring. – Lieven Keersmaekers Sep 10 '11 at 19:44
  • @Lieven: it's not *a* thread, it's any and all of them. I'm playing music, (unrelated to all these Explorer threads,) and when they clean up they make the music pop. – Mason Wheeler Sep 10 '11 at 19:46
  • @Mason: I don't understand why that would happen... Old computer and/or OS (that is, XP)? – Andreas Rejbrand Sep 10 '11 at 19:48
  • @Mason - iTunes by any chance? – Lieven Keersmaekers Sep 10 '11 at 19:48
  • @Andreas: OK, on further testing, apparently it's only happening under the debugger. So not such a big deal after all. – Mason Wheeler Sep 10 '11 at 19:59
  • @Lieven: No, libmodplug actually. – Mason Wheeler Sep 10 '11 at 20:00
  • @Mason - iTunes add's a system sound for a `Page Load Complete`. This also made some kind of popping sound driving me nuts (axloadcomplete.wav to be exact). Removing the sound from the event resolved that for me and I used the method described to find what was producing the sound in the first place. If it's not bothering you, it's not worth the time trying to search if libmodplug has an identical *feature*. – Lieven Keersmaekers Sep 10 '11 at 20:05
  • @Lieven: No, it's nothing like that. Libmodplug is a simple music decoding library, and the only sounds it produces are the ones that are in the music file. – Mason Wheeler Sep 10 '11 at 20:06
  • That's the answer, @Luke. Please post it as an answer. – Rob Kennedy Sep 10 '11 at 22:34
  • Windows shell happens, what else? You got fully functional `explorer` view in your dialog, hence the overhead. – Premature Optimization Sep 11 '11 at 03:02
  • @Mason Wheeler: Increase priority of your music playing thread. – Torbins Sep 11 '11 at 11:33

1 Answers1

8

Imagine you want to show your friends how beautiful the Pacific Northwest is. You decide to set off on a trip to snap a few photos of sunset over the Pacific. What you really care about is the image files making their way home, where they can be uploaded to the Facebook. In reality the camera, lenses and the tripod need to be hauled over the Olympics and back. You also need to bring the photographer (yourself) who will set the camera up and press the shutter. The photographer needs to be moved there and back in relative comfort, so you take a seat on which the photographer will rest while making the trip. This seat is enclosed in a shiny metal box with a bunch of other metal, glass and rubber parts some of which are turning and reciprocating. In the end, about two tons of stuff (and a living human being) taking a multi-hour trip, burning gallons of hydrocarbon liquid -- with the goal of moving a few bits of information from the shore to the internet.

Exactly the same thing happens with your application. When the user wants to open a file using "Open File" dialog box, the user expects to be able to:

  • navigate to the directory containing the file (The directory may be on local hard drive or CD/DVD/BR or network drive or archive, etc. The media may be encrypted or compressed, which needs to be displayed differently. The media may not be plugged in, for which the user might need to be prompted. The media may require user's credentials, which have to be asked);
  • connect to a new directory using its URI/UNC (map the drive);
  • search the directory for some keywords;
  • copy/delete/rename some files;
  • see the list of the files in that directory;
  • preview the content of each file in the directory;
  • select which file to open;
  • change his/her mind and decide not to open the file;
  • do many other file-related things.

The OS lets all this to happen by essentially giving your process most of the Windows Explorer functionality. And some of it has to happen in the background, otherwise the users will complain about how unresponsive the Open File dialog is. The obvious way to run some tasks in background is to run them on different threads. So that's what we see.

What about the threads left behind, you ask? Well, some of them are left there for the case the user will decide to open another file: it saves a lot of time, traffic and typing in this case. That custom authentication used for this one particular process last time? -- stored. The preview icons for those pesky PDFs? -- still there. The length and bitrate for every movie in the directory? -- still available, no need to re-parse them.

Of course the threads did not just magically appear by themselves. Check out how many DLLs have been mapped into the process. Looking at some of them one can get quite an interesting picture of what functionality has been added.

Another interesting way to look at it would be to dump the callstacks at the moment every thread gets created. This shows which DLL (and sometimes which object) created them. Here's how a x64 Win7 creates all the threads. One can find the Explorer frame's thread getting created; some OLE activity which will be used to instantiate file filters, some of which can generate preview icons, overlays and tooltips; few threads belonging to search subsystem; shell's device enumerator (so if the user plugs in a new device, it will automatically appear in the open dialog); shell network monitor (ditto) and other stuff.

The good news is it happens fast and doesn't add too much overhead to your process. Most of the threads spend most of the time waiting for some seldom events (like USB key being plugged in), so the CPU doesn't spend any time executing them. Each thread consumes 1MB of virtual address space in your process, but only few 4Kb pages of actual physical memory. And most, if not all of those DLLs did not use any disk bandwidth to be loaded: they were already in RAM, so they just got mapped into your process for almost free.

In the end the user got a whole lot of useful functionality in a snappy UI, while the process had to do very little to achieve all that.

Rom
  • 4,129
  • 23
  • 18