为什么尝试打开 TOpenDialog 会产生大量线程?
我有一个非常简单的表单,上面有一个 TOpenDialog 和一个按钮。当我按下按钮时,它会在对话框中调用“执行”。如果我在调试器中观察,打开对话框的行为会产生大约 14 个线程,并且当我关闭对话框时它们也不会消失。
有人知道这是怎么回事吗?
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
想象一下,您想向您的朋友展示太平洋西北地区有多么美丽。您决定出发去拍摄一些太平洋日落的照片。您真正关心的是图像文件正在回家,可以将它们上传到 Facebook。事实上,相机、镜头和三脚架需要在奥运会上拖来拖去。您还需要带上摄影师(您自己),他将设置相机并按下快门。摄影师需要在相对舒适的情况下来回移动,因此您在旅途中需要坐在摄影师休息的座位上。这个座椅被封装在一个闪亮的金属盒子里,里面有一堆其他金属、玻璃和橡胶部件,其中一些部件正在旋转和往复运动。最后,大约两吨的东西(和一个活人)进行了几个小时的旅行,燃烧了几加仑的碳氢化合物液体——目的是将一些信息从岸上转移到互联网上。
您的应用程序也会发生完全相同的情况。当用户想要使用“打开文件”对话框打开文件时,用户希望能够:
操作系统通过本质上为您的进程提供大部分 Windows 资源管理器功能来实现这一切。其中一些必须在后台发生,否则用户会抱怨“打开文件”对话框的响应速度如何。在后台运行某些任务的明显方法是在不同的线程上运行它们。这就是我们所看到的。
您问留下的线索怎么办?嗯,其中一些保留在用户决定打开另一个文件的情况下:在这种情况下,它可以节省大量时间、流量和打字。上次用于此特定过程的自定义身份验证? ——存储。那些烦人的 PDF 的预览图标? ——还在那里。目录中每部电影的长度和比特率? -- 仍然可用,无需重新解析它们。
当然,这些线索并不是自己神奇地出现的。检查有多少DLL被映射到进程中。查看其中的一些内容,人们可以对添加的功能有一个非常有趣的了解。
另一种有趣的看待它的方法是在每个线程创建时转储调用堆栈。这显示了哪个 DLL(有时是哪个对象)创建了它们。 以下是 x64 Win7 如何创建所有线程。人们可以发现资源管理器框架的线程正在被创建;一些 OLE 活动将用于实例化文件过滤器,其中一些可以生成预览图标、覆盖层和工具提示;属于搜索子系统的线程很少; shell 的设备枚举器(因此,如果用户插入新设备,它将自动出现在打开的对话框中); shell 网络监视器(同上)和其他东西。
好消息是它发生得很快,并且不会给您的流程增加太多开销。大多数线程花费大部分时间等待一些很少发生的事件(例如 USB 密钥插入),因此 CPU 不会花任何时间执行它们。每个线程消耗进程中 1MB 的虚拟地址空间,但只消耗很少的 4Kb 页的实际物理内存。大多数(如果不是全部)这些 DLL 都没有使用任何磁盘带宽来加载:它们已经在 RAM 中,因此它们几乎免费地映射到您的进程中。
最终,用户在快速的 UI 中获得了大量有用的功能,而流程只需做很少的事情即可实现所有这些。
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:
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.