Thread.Synchronize

A general mistake many Delphi developers are making is accessing GUI classes from other threads directly (in a multi-threaded application). It’s a mistake I was doing for a long time. Eventually I started using SendMessage to synchronize with the main thread (the GUI one) by passing a set of WM_PLEASE_DO_THAT messages that carried some informational value to a specific message method.

Now, if you’re coming from .NET world you’re probably used to Control.Invoke method to synchronize your GUI from a separate thread. Actually .NET visual classes require that the same thread is used to access the GUI all the time — otherwise you will be presented with an exception telling you’re not doing it right: a method that will teach make you do it right :). Well in Delphi’s case it’s not that simple: you won’t get any exceptions, and most of the time everything will work just fine; but in rare cases it will simply break. This was the case with Eduka+, a program that used a few threads that accessed the GUI directly. It works on single-core CPUs but breaks down on multi-core ones. Eventually I had to turn on a special flag in the PE header to tell windows to use a single core.

Anyway, Delphi posseses the required functionality to create safe applications in form of TThread.Synchronize and TThread.Queue methods. Both methods delegate a routine to be executed on the main thread. The difference between Synchronize and Queue is that the first method will wait until the delegated routine has finished executing on the main thread and the second method will not wait — something resembling the behavior of SendMessage and PostMessage in Windows world.

Now, let’s see what changes should be made to unsafe code to make it safe (using Anonymous methods i Delphi 2009). Consider this code:

procedure Unsafe_FunctionExecuteOnOtherThread();
var
  AResult : Integer;
begin
  AResult := DoSomeComputations();
  CheckSomething();
  
  { Update the GUI }
  Form1.Edit1.Text := IntToStr(AResult);
end;

The safe variant of the code looks like this:

procedure Safe_FunctionExecuteOnOtherThread();
var
  AResult : Integer;
begin
  AResult := DoSomeComputations();
  CheckSomething();

  { Update the GUI }
  TThread.Synchronize(nil, procedure begin
    Form1.Edit1.Text := IntToStr(AResult);
  end);
end;

That’s basically all you have to do! Of course you have to decide now if you want to wait for the GUI update to be finished or not before continuing. Also note that using Queue could be tricky because AResult variable used in the function and in the anonymous method uses the same memory location 😉

You May Also Like

About the Author: Alexandru Ciobanu

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.