Using EnterCriticalSection in Thread to update VCL

2019-06-25 09:31发布

问题:

I'm new to threads. I'm using a 3rd party library that uses threads which at times call a procedure I've provided.

How do I update update a TLabel.Caption from my procedure when its called by the thread?

If I've called InitializeCriticalSection elsewhere, is it as simple as

  EnterCriticalSection(CritSect);
  GlobalVariable := 'New TLabel.Caption';
  LeaveCriticalSection(CritSect);

And then in my main thread:

  EnterCriticalSection(CritSect);
    Label1.Caption:= GlobalVariable;
  LeaveCriticalSection(CritSect);

But, how do I get the main thread code to be called? The thread can use SendMessage? Or is there some better/easier way (.OnIdle could check a flag set by the thread?)

Thanks.

回答1:

Critical Sections are used to serialize accessing to a piece of code. For updating graphical user interface, you should take note that only the main thread should update GUI elements.

So if your thread needs to update a GUI element, it should delegate this to the main thread. To do so, you can use different techniques:

The simplest one is using Synchronize method in your thread code. When Synchronize is called, your thread is paused, the code you provided to Synchronize will be executed in the context of the main thread, and then your thread resumes.

If you don't like your thread get stopped every time that piece of code is called, then you can use Queue method. Queue sends your request to the message queue of the destination thread (here main thread), so your thread will not stop, but the UI might not get updated immediately, depending on how crowded main thread's message queue is.

Another way to achieve this is to send custom Windows messages to the main thread using SendMessage or PostMessage API functions. In that case, you have to define a custom message, and send it to the main thread whenever you need to change a UI element. Your main thread should provide a message handler for that type of message, and handle the received messages. The consequence is something similar to using Queue method.



回答2:

To make your code gets called in the main thread, take a look at TThread.Synchronize. It accepts a method pointer (or, in D2009+, an anonymous method) and takes care of all the messaging behind the scenes to make sure your code will run in the main thread.



回答3:

You must ensure that the label is updated in a safe manner. Furthermore VCL runs in the application main thread and messing with it from other threads can have weird results. Even if using critical sections.

So my advice is: just use PostMessage. When your callback procedure is called, just call PostMessage from that procedure to the main form window handle. This will ensure that the label caption is set in the context of the main thread.

Code sample:

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;

But you have to be careful if you pass strings around this way. You have to synchronize the access to such variable. Or you can use GlobalAddAtom (which is a little deprecated), or something similar.

EDIT:

As Mason already said you can also use Synchronize which is easier to use. For your problem it should be perfectly OK.

EDIT 2:

For the reference how to use GlobalAddAtom (yes I misspelled it earlier):

http://www.delphi3000.com/articles/article_574.asp?SK=



回答4:

The way I do this is to have the main application thread use a TTimer on the form to check the status of a thread specific value to see if the status has changed, and if so, update the label (or other components). The TThread descendent uses a property with Getter function to protect access with the critical section. This way the work thread is never held back by the main thread (which Synchronize will do), and the user does not experience any delay in the use of the UI.