从创建UI的同一线程更新VCL。为什么?

8

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

我经常听到“不线程安全”的说法,但找不到实际的解释。

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

请为这个话题提供一些启示。


3
文档中以前有一些关于在主 VCL 线程中使用线程的说明。但是在当前的文档中,我找不到类似的内容。请参考以下链接:http://docwiki.embarcadero.com/RADStudio/en/How_To_Build_Multithreaded_Applications。 - Sertac Akyuz
3个回答

12

造成 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系统的正确使用方式。


8
关于Windows中的GDI线程安全性,请参考此参考文章。它明确指出,您可以从多个线程安全地访问句柄,但不应同时进行访问。您需要保护对GDI句柄的访问,例如使用关键部分。

请记住,像大多数Windows句柄一样,GDI句柄是映射到integer(新版Windows下的NativeUInt,用于64位兼容性)的内部结构的指针。在多线程计算中,像往常一样并发访问相同的内容可能会造成问题,这些问题非常难以识别和修复。

从一开始,VCL本身的UI部分就不打算支持线程安全,因为它依赖于非线程安全的Windows API。例如,如果您在一个线程中释放了在另一个线程中仍然需要的GDI对象,则可能会遇到潜在的GPF问题。

Embarcadero(当时)本可以使VCL线程安全,通过关键部分对所有UI访问进行串行化,但这可能会增加复杂性并降低整体性能。请注意,即使在Microsoft .Net平台(在WinFormsWPF中),也需要专用线程进行UI访问,据我所知。

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

  1. 从线程使用Synchronize调用;
  2. 从后台线程发送GDI自定义消息(请参见WM_USER),以通知UI线程需要刷新;
  3. 采用无状态方法:UI将会定期从逻辑层刷新其内容(使用计时器或更改数据的某些按钮时)。
在我看来,对于大多数UI,我更喜欢选项2,并为远程客户端-服务器访问提供其他选项3(可以与选项2混合使用)。因此,您不必从服务器端触发一些更新事件到UI。在HTTP / AJAX RESTful世界中,这确实是有意义的。选项1有点慢,依我之见。在所有情况下,选项2和3都需要清晰的n-Tier分层架构,其中逻辑和UI不混合:但这是任何严肃开发所应遵循的良好模式。

2
它明确说明您可以从多个线程安全地访问句柄,但不应同时进行。它并没有这样说。只要您的访问是只读的,那么多个线程可以同时访问GDI、窗口属性等。在谈论线程安全时,不精确是毫无意义的。如Eric Lippert所指出的那样,单独使用术语“线程安全”是没有意义的:http://blogs.msdn.com/b/ericlippert/archive/2009/10/19/what-is-this-thing-you-call-thread-safe.aspx - David Heffernan
2
从技术角度来看,你是正确的。我考虑到访问GDI句柄主要是为了向它发送消息,并可能分配和释放相关资源(字体、笔等)。我的建议/规则是为了确保访问是安全的,通过阻止同时访问,特别是通过VCL层。向GDI实例发送消息是线程安全的,但分配/释放GDI资源和设置参数不是-这就是所写的内容。您可以通过混合消息来混合GDI命令,这可能会产生意外的行为。因此,“一次一个”。 - Arnaud Bouchez
1
请参阅Remy在此处的评论:VCL不是线程安全的真正含义是什么? - LU RD
Java的各种框架也只有一个线程可以访问UI。这几乎是普遍的现象。 - Warren P

4

带有句柄的Windows控件不是线程安全的(即不能同时由两个不同的线程安全地访问),而Delphi将Windows控件封装为VCL控件。由于控件是由主 GUI 线程访问的,因此如果您正在执行另一个线程,则需要将它们保持原样。


2
这比较复杂。总的来说,Windows API是线程安全的。窗口属性可以同时从多个线程读取。修改应该限制在与窗口关联的线程中。 - David Heffernan
1
@David,是的,我确定那是正确的,但我们面对现实吧,最简单的方法就是假设只有单线程访问。你可以想象得到的和实际可行的往往是两回事;-) - Misha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接