7

I have to modify and change some visual components in a thread and as you know it's not safe to doing this.

My question is how to write a completely thread-safe code? It is possible? if it is then can you please give me a simple example?

my code that is not threadsafe:

type
  tMyWorkerThread = class(TThread)
      public
         procedure Execute; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure tMyWorkerThread.Execute;
begin
  //codes
  //working with visual components
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  TMyWorkerThread.Create(false);
end;

Thank you.

Sky
  • 4,244
  • 7
  • 54
  • 83
  • 2
    have a look at [Synchronize](http://docwiki.embarcadero.com/Libraries/XE4/en/System.Classes.TThread.Synchronize) – bummi Jul 17 '13 at 16:27
  • 2
    Use the `File->New->Other` menu, choose `Delphi Projects->Delphi Files->Thread Object`, and read the *huge comment* at the top of the new unit it creates. – Ken White Jul 17 '13 at 16:27
  • *I have to modify and change some virtual component* Virtual component or visual component ? And what kind of update ? progress bar may easily be done from main thread while worker trreads would only report their work - that is done in few lines of code – Arioch 'The Jul 17 '13 at 16:57
  • possible duplicate of [Update a VCL component from CreateAnonymousThread](http://stackoverflow.com/questions/6739595/update-a-vcl-component-from-createanonymousthread) – Arioch 'The Jul 17 '13 at 17:18

3 Answers3

10

Writing a thread safe code in Delphi involves the basic care you would have in any other language, which means to deal with race conditions. A race condition happens when different threads access the same data. A good way to deal with that is to declare an instance of TCriticalSection and wrap the dangerous code in it.

The code below shows a getter and a setter of a property that, by hypotesis, has a race condition.

constructor TMyThread.Create;
begin
  CriticalX := TCriticalSection.Create;
end;

destructor TMyThread.Destroy; override;
begin
  FreeAndNil(CriticalX);
end;

function TMyThread.GetX: string;
begin
  CriticalX.Enter;
  try
    Result := FX;
  finally
    CriticalX.Leave;
  end;
end;

procedure TMyThread.SetX(const value: string);
begin
  CriticalX.Enter;
  try
    FX := Value;
  finally
    CriticalX.Leave;
  end;
end;

Notice the use of a single instance of TCriticalSection (CriticalX) to serialize the access to the data member FX.

However, with Delphi you have an aditional consideration! VCL is not thread safe, so in order to avoid VCL race conditions, any operation that results in screen changing must run in the main thread. You get that by calling such a code inside a Synchronize method. Considering the class above, you should do something like this:

procedure TMyThread.ShowX;
begin
  Synchronize(SyncShowX);
end;

procedure TMyThread.SyncShowX;
begin
  ShowMessage(IntToStr(FX));
end;

If you have Delphi 2010 or later, there is an easier way that makes use of anonymous methods:

procedure TMyThread.ShowX;
begin
  Synchronize(procedure begin
    ShowMessage(IntToStr(FX));
  end);
end;

I hope this helps!

AlexSC
  • 1,823
  • 3
  • 28
  • 54
  • Can you please stop calling **critical section** s - and even **event** s in an other post - **Mutex**? It could be a source of confusion if you know what a Mutex is, and can not help understand your code if you don't. – mg30rg Nov 28 '13 at 13:57
5

You should only access VCL objects from main VCL thread.

Some reading methods (property getters) do work from other threads in practice - but you have to prove it in advance reading VCL sources for the specific Delphi build. Or not use it.

PS: Synchronize method runs given procedure in main VCL thread, pausing the caller thread, which may lead to a deadlock, if main thread was also blocked.

Read more: (actually making this answer to list some links)

Community
  • 1
  • 1
Arioch 'The
  • 15,799
  • 35
  • 62
  • 2
    Good list! "Multithreading - The Delphi Way" by Martin Harvey should be up there as well. In my opinion at the top position :-D Unfortunately the [original](http://www.pergolesi.demon.co.uk/prog/threads/ToC.html) is no longer around but there are several "cached" copies around: [Thaddy's](http://thaddy.co.uk/threads/) and [http://web.archive.org/](http://web.archive.org/web/20060305174604/http://www.pergolesi.demon.co.uk/prog/threads/ToC.html) are the ones I know about. It is also available through [Code Central](http://cc.embarcadero.com/item/14809). – Marjan Venema Jul 17 '13 at 17:38
  • @MarjanVenema okay. Your link is rather valueable cause actually this list of mine is just 5 minutes of googling, and your links requires prior knowledge. – Arioch 'The Jul 18 '13 at 13:40
  • 1
    In case Martin's fine write-up gets lost to URL rot I have it posted on my web site: http://www.seti.net/engineering/threads/threads.php – Seti Net Jun 11 '16 at 06:10
  • Well, but tdictionary is RTL. – Marco van de Voort Jun 26 '17 at 09:50
-2

My problem solved with Synchronize!

type
  tMyWorkerThread = class(TThread)
      public
         procedure Execute; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure tMyWorkerThread.Execute;
begin

  //codes that takes long time
  Synchronize(procedure begin
     //working with visual components
  end
  );

end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  TMyWorkerThread.Create(false);
end;

Thank you all for helping me.

Sky
  • 4,244
  • 7
  • 54
  • 83
  • 13
    Sad that you would add and accept your own answer rather than one of the other answers which are telling you the same thing. Really breaks the flow of how Stack Overflow works. – Jerry Dodge Feb 04 '15 at 05:21