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

Multithreading 从创建UI的同一线程更新VCL。为什么?,multithreading,delphi,thread-safety,Multithreading,Delphi,Thread Safety,我知道我必须调用Synchronize来从一个没有创建控件或向窗口发送消息的线程更新vcl 我经常听到“不线程安全”这个词,但我找不到一个实际的解释 我知道应用程序可能会因访问冲突而崩溃,但我也不知道为什么 请说明此主题。带有句柄的Windows控件不是线程安全的(即,它们不能由两个不同的线程同时安全访问),Delphi包装Windows控件以提供VCL控件。由于控件是由主GUI线程访问的,因此,如果您正在执行另一个线程,则需要将它们放在一边。关于Windows中的GDI线程安全 它明确指出,您

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

我经常听到“不线程安全”这个词,但我找不到一个实际的解释

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


请说明此主题。

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

关于Windows中的GDI线程安全


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

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

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

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

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

  • 使用
    同步来自线程的调用
    
  • 从后台线程发送一条GDI自定义消息(请参见
    WM_USER
    ),通知UI线程需要刷新
  • 采用无状态方法:UI将不时地从逻辑层刷新其内容(使用计时器或当您按下一些可能更改数据的按钮时)

  • 在我看来,对于大多数UI,我更喜欢选项2,对于远程客户端服务器访问,我更喜欢附加选项3(可以与选项2混合使用)。因此,您不必希望从服务器端触发一些UI更新事件。在HTTP/AJAX世界中,这确实是有意义的。选项1有点慢,IMHO。在所有情况下,选项2和3都希望有一个清晰的、逻辑和UI不混合的模式:但对于任何严肃的开发来说,这都是一个很好的模式。

    VCL UI控件中线程不安全的最大原因之一是
    TWinControl.Handle
    属性getter。它不仅仅是控件的
    HWND
    的简单只读访问器。如果它还不存在,它还会创建
    HWND
    。如果工作线程在没有
    HWND
    的情况下读取
    Handle
    属性,它会在工作线程上下文中创建一个新的
    HWND
    ,这是错误的,因为
    HWND
    s与创建线程上下文绑定,这将使拥有的控件最多只能运行,因为控件的Windows消息将不再通过主消息循环。但更糟糕的是,如果主线程在工作线程执行操作的同时读取相同的
    句柄
    属性(例如,如果主线程出于各种原因动态地重新创建
    句柄
    ),存在一个争用条件,线程上下文在这两个条件之间创建被指定为新的
    句柄的
    HWND
    ,如果两个线程最终都创建了新的
    HWND
    ,但只有一个线程可以保留,而另一个线程则可能会泄漏句柄

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


    如果您可以确保提前分配控件的
    句柄
    ,并且如果您可以确保主线程在工作线程运行时从不重新创建
    句柄
    ,则可以从工作线程安全地向该控件发送消息,而无需使用
    同步()。但这是不可取的,工作线程需要考虑的因素太多了。这就是为什么最好只在主线程中进行所有UI访问。这就是VCL用户界面系统的使用方式。

    以前在手册中有一些解释。不知怎的,我在电流中找不到任何类似的东西。它比这更复杂。总的来说,Windows API是线程安全的。可以同时从多个线程读取窗口属性。修改应该局限于窗口具有关联性的线程。@David,是的,我确信这是正确的,但是面对现实,最简单的方法是假设只有单线程访问。你能想到的和实际可行的往往是两件不同的事情;-)“它清楚地表明,您可以安全地访问