我知道必须调用Synchronize方法才能从未创建控件或向窗口发送消息的线程更新vcl。
我经常听到“不线程安全”的说法,但找不到实际的解释。
我知道应用程序可能会因为访问冲突而崩溃,但我不知道具体原因是什么?
请为这个话题提供一些启示。
我知道必须调用Synchronize方法才能从未创建控件或向窗口发送消息的线程更新vcl。
我经常听到“不线程安全”的说法,但找不到实际的解释。
我知道应用程序可能会因为访问冲突而崩溃,但我不知道具体原因是什么?
请为这个话题提供一些启示。
造成 VCL UI 控件线程不安全的最大原因之一是 TWinControl.Handle
属性获取器。它不仅是控件的 HWND
的简单只读访问器,还会在不存在 HWND
时创建它。如果一个工作线程在没有 HWND
的情况下读取 Handle
属性,它会在工作线程上下文中创建一个新的 HWND
,这很糟糕,因为 HWND
与创建线程上下文绑定,这将使拥有控件基本上无法使用,因为控件的 Windows 消息将不再通过主消息循环传递。但更糟糕的是,如果主线程同时读取相同的 Handle
属性(例如,如果主线程出于任何原因动态重新创建 Handle
),则存在哪个线程上下文创建作为新 Handle
分配的 HWND
的竞争条件,以及可能的句柄泄漏潜在性,如果两个线程都创建新的 HWND
但只能保留一个,另一个将被泄漏。
另一个导致线程不安全的罪魁祸首是 VCL 的 MakeObjectInstance()
函数,它在 VCL 内部用于将 TWinControl.WndProc()
非静态类方法指定为 TWinControl.Handle
窗口的消息过程,并将任何 TWndMethod
类型的对象方法指定为由 AllocateHWnd()
函数创建的 HWND
的消息过程(例如,TTimer
使用)。 MakeObjectInstance()
对一些内存分配/缓存以及这些内存内容的调整进行了相当多的操作,这些操作没有受到多个线程的并发访问的保护。
如果您可以提前确保控件的Handle
已经分配,并且您可以确保主线程在工作线程运行时不会重新创建该Handle
,那么就 有可能 在不使用Synchronize()
的情况下从工作线程向该控件安全地发送消息。但这并不建议,因为工作线程需要考虑太多的因素。这就是为什么最好只在主线程中进行所有UI访问,这也是VCL UI系统的正确使用方式。
integer
(新版Windows下的NativeUInt
,用于64位兼容性)的内部结构的指针。在多线程计算中,像往常一样并发访问相同的内容可能会造成问题,这些问题非常难以识别和修复。
从一开始,VCL本身的UI部分就不打算支持线程安全,因为它依赖于非线程安全的Windows API。例如,如果您在一个线程中释放了在另一个线程中仍然需要的GDI对象,则可能会遇到潜在的GPF问题。
Embarcadero(当时)本可以使VCL线程安全,通过关键部分对所有UI访问进行串行化,但这可能会增加复杂性并降低整体性能。请注意,即使在Microsoft .Net平台(在WinForms和WPF中),也需要专用线程进行UI访问,据我所知。
因此,要从多个线程刷新UI,您有几种模式:
Synchronize
调用; WM_USER
),以通知UI线程需要刷新; 带有句柄的Windows控件不是线程安全的(即不能同时由两个不同的线程安全地访问),而Delphi将Windows控件封装为VCL控件。由于控件是由主 GUI 线程访问的,因此如果您正在执行另一个线程,则需要将它们保持原样。