8

我知道我必须调用 Synchronize 从没有创建控件或向窗口发送消息的线程更新 vcl。

我经常听到“线程不安全”这个词,但我找不到关于正在发生的事情的实际解释。

我知道应用程序可能会因访问冲突而崩溃,但我还是不知道为什么?

请阐明这个话题。

4

3 回答 3

12

VCL UI 控件中线程不安全的最大原因之一是TWinControl.Handle属性 getter。它不仅仅是控件的简单只读访问器HWNDHWND如果它还不存在,它也会创建。如果工作线程Handle在不存在的情况下读取该属性HWND,它会在工作线程上下文中创建一个新HWND的,这很糟糕,因为HWNDs 与创建线程上下文相关联,这将导致拥有控制在最好的情况下几乎无法操作,因为 Windows 消息控件将不再通过主消息循环。但更糟糕的是,如果主线程在Handle工作线程读取相同属性的同时(例如,如果主线程动态地重新创建HandleHWND出于多种原因),在线程上下文创建被分配为 new的线程之间存在竞争条件,并且如果两个线程最终都创建 new s 但只能保留一个Handle线程,则可能存在潜在的句柄泄漏HWND被泄露。

线程不安全的另一个问题是 VCL 的MakeObjectInstance()函数,VCL 内部使用该函数将TWinControl.WndProc()非静态类方法分配为TWinControl.Handle窗口的消息过程,以及将任何TWndMethod类型的对象方法分配为由HWND创建的消息过程AllocateHWnd()功能(TTimer例如使用)。 MakeObjectInstance()对那些不受多线程并发访问保护的内存内容进行了相当多的内存分配/缓存和旋转。

如果您可以确保Handle提前分配控件,并且如果您可以确保主线程Handle在工作线程运行时永远不会重新创建它,那么可以安全地从工作线程向该控件发送消息,而无需使用Synchronize(). 但这是不可取的,工作线程必须考虑的因素太多了。这就是为什么最好只在主线程中完成所有UI 访问。这就是 VCL UI 系统的用途。

于 2012-05-18T15:16:44.477 回答
8

关于 Windows 中的 GDI 线程安全,请参阅此参考文章

它明确指出您可以从多个线程安全地访问句柄,不应同时进行。您需要保护对 GDI 句柄的访问,例如使用临界区。

请记住,与大多数 Windows 句柄一样,GDI 句柄是映射到(在较新的 Windows 下,为了 64 位兼容性)的内部结构的指针。就像在多线程计算中一样,同时访问相同的内容可能是问题的根源,这些问题很难识别和修复。integerNativeUInt

VCL 本身的 UI 部分从一开始就不是线程安全的,因为它依赖于非线程安全的 Windows API。例如,如果您在一个线程中释放一个 GDI 对象,而另一个线程仍需要该对象,您将面临潜在的 GPF。

Embarcadero(此时)本可以使 VCL 线程安全,通过关键部分序列化所有 UI 访问,但它可能增加了复杂性,并降低了整体性能。请注意,即使是 Microsoft .Net 平台(在WinFormsWPF中)也需要用于 UI 访问的专用线程 AFAIK。

因此,要从多个线程刷新 UI,您有几种模式:

  1. 使用Synchronize来自线程的调用;
  2. 从后台线程发送 GDI 自定义消息(请参阅 参考资料WM_USER)以通知 UI 线程需要刷新;
  3. 采用无状态方法:UI 会不时地从逻辑层刷新其内容(使用计时器或当您按下一些可能会更改数据的按钮时)。

从我的角度来看,对于大多数 UI,我更喜欢选项 2,而对于远程客户端-服务器访问,我更喜欢选项 3(可以与选项 2 混合使用)。因此,您不必希望从服务器端触发 UI 的某些更新事件。在 HTTP/AJAX RESTful世界中,这确实是有意义的。选项 1 有点慢,恕我直言。在所有情况下,选项 2 和 3 期望一个清晰的n 层分层架构,其中逻辑和 UI 没有混合:但无论如何,对于任何严肃的开发来说,这是一个很好的模式。

于 2012-05-18T12:19:12.077 回答
4

带有句柄的 Windows 控件不是线程安全的(即它们不能被两个不同的线程同时安全地访问),Delphi 封装了 Windows 控件以提供 VCL 控件。由于控件是由主 GUI 线程访问的,因此如果您正在执行另一个线程,则需要不理会它们。

于 2012-05-18T09:17:07.827 回答