Delphi 在线程中使用EnterCriticalSection更新VCL标签

Delphi 在线程中使用EnterCriticalSection更新VCL标签,delphi,synchronization,thread-safety,vcl,Delphi,Synchronization,Thread Safety,Vcl,我是新手。我正在使用一个第三方库,它使用线程,这些线程有时会调用我提供的过程 当线程调用TLabel.Caption时,如何从过程中更新它 如果我在别处调用InitializeCriticalSection,它是否像 EnterCriticalSection(CritSect); GlobalVariable := 'New TLabel.Caption'; LeaveCriticalSection(CritSect); 然后在我的主线中: EnterCriticalSecti

我是新手。我正在使用一个第三方库,它使用线程,这些线程有时会调用我提供的过程

当线程调用TLabel.Caption时,如何从过程中更新它

如果我在别处调用InitializeCriticalSection,它是否像

  EnterCriticalSection(CritSect);
  GlobalVariable := 'New TLabel.Caption';
  LeaveCriticalSection(CritSect);
然后在我的主线中:

  EnterCriticalSection(CritSect);
    Label1.Caption:= GlobalVariable;
  LeaveCriticalSection(CritSect);
但是,如何调用主线程代码?线程可以使用SendMessage吗?或者是否有更好/更简单的方法(.OnIdle可以检查线程设置的标志?)


谢谢。

要使代码在主线程中被调用,请查看TThread.Synchronize。它接受一个方法指针(或者,在D2009+中是一个匿名方法),并负责所有幕后消息传递,以确保代码在主线程中运行。

您必须确保以安全的方式更新标签。此外,VCL在应用程序主线程中运行,并且从其他线程对其进行处理可能会产生奇怪的结果。即使使用关键部分

所以我的建议是:只使用PostMessage。调用回调过程时,只需将该过程中的PostMessage调用到主窗体窗口句柄。这将确保在主线程的上下文中设置标签标题

代码示例:

type
  TForm1 = class(TForm)
  private
    procedure OnWMUpdateLabel(var Msg: TMessage); message WM_UPDATE_LABEL;
    procedure MyCallbackProcedure(const Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.OnWMUpdateLabel(var Msg: TMessage);
begin
  Label1.Caption := SomeVariable;
end;

procedure TForm1.MyCallbackProcedure(const Sender: TObject);
begin
  SomeVariable := 'New Label';
  PostMessage(Handle, WM_UPDATE_LABEL, 0, 0);
end;
但是如果你用这种方式传递字符串,你必须小心。您必须同步对此类变量的访问。或者您可以使用GlobalAddAtom(有点不推荐),或者类似的东西

编辑:

正如Mason已经说过的,您还可以使用更易于使用的Synchronize。对于你的问题,它应该是完美的

编辑2:

关于如何使用GlobalAddAtom的参考(是的,我之前拼错了):


关键部分用于序列化对一段代码的访问。为了更新图形用户界面,您应该注意只有主线程应该更新GUI元素

因此,如果您的线程需要更新GUI元素,它应该将其委托给主线程。为此,您可以使用不同的技术:

最简单的方法是在线程代码中使用Synchronize方法。调用Synchronize时,线程将暂停,提供用于同步的代码将在主线程的上下文中执行,然后线程将恢复

如果您不喜欢在每次调用该代码段时停止线程,那么可以使用队列方法。Queue将您的请求发送到目标线程(此处为主线程)的消息队列,因此您的线程不会停止,但UI可能不会立即更新,这取决于主线程的消息队列有多拥挤


实现这一点的另一种方法是使用SendMessage或PostMessageAPI函数将自定义Windows消息发送到主线程。在这种情况下,您必须定义一条自定义消息,并在需要更改UI元素时将其发送到主线程。主线程应该为该类型的消息提供一个消息处理程序,并处理收到的消息。其结果类似于使用队列方法。

我这样做的方式是让主应用程序线程在表单上使用TTimer来检查线程特定值的状态,以查看状态是否已更改,如果已更改,则更新标签(或其他组件)。TThread子代使用带有Getter函数的属性来保护对critical部分的访问。这样,工作线程就不会被主线程阻止(同步可以),用户在使用UI时也不会遇到任何延迟。

唯一的问题是,他的回调是否是线程的一部分,因此他可以直接访问同步化。我不知道这个函数的调用是否超出了TThread的范围。这就是为什么我发布了带有PostMessageThread的代码,它有一个类过程,也称为Synchronize,它接受线程和回调作为参数,您可以将nil作为线程传递。是的,我的回调不是线程的一部分,所以Synchronize不可用。@Craig,cool不知道他们添加了这个。我不经常使用同步;)你是对的-我的回调不是线程的一部分,因此同步不可用。线程由第三方库启动。。。我找不到如何使用AddGlobalAtom,所以我决定使用您的PostMessage解决方案,只需传递一个单词来标识我将使用的几个标题之一。谢谢。你仍然可以使用
Synchronize
,因为它是一个静态方法。顺便说一句@Runner:什么是“有点不推荐”?:)PostMessage可能是线程间通信的一个重要开销。如果您想偶尔更新标签标题,但不要将其用于大规模通信,这将使您的应用程序几乎无法使用,这并不是一个真正的问题。用户257188:既然你接受了这个答案,你也应该意识到潜在的陷阱:像这样使用
Handle
可能是危险的,因为从辅助线程读取它会调用VCL类中的属性getter。您必须确保句柄在线程处于活动状态时始终有效,并且可以调用回调。这意味着在创建线程之前调用
HandleNeeded
,在释放表单之前终止线程,并且不会导致重新创建表单句柄。例如,更改表单的保持在顶部会导致出现这种情况……”您的主线程应该为该类型的消息提供一个消息处理程序,并处理接收到的消息。“啊,这很复杂。如何使用消息安全地从线程传递字符串@user257188:这只是
PostMessage()
的一个问题。使用
SendMessage()
处理消息时,字符串将有效。