Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Bob Swart (aka Dr.Bob) - Medical Officer Delphi in a Nutshell
 Under The Hood #4 - Is the VCL thread-safe?
See Also: Delphi Papers and Dr.Bob's Examines Columns

This time, we're taking a closer look at Delphi 2 and the inner workings of 32-bit Windows. Win32, a true pre-emptive operating system ads the new concept of multi-threading to our arsenal of programming techniques.
However, with each new technique, we must first master it before we can add it to our daily set of tools. So also with multi-threading. And one of the questions that comes up when associating threads with Delphi is the question: "Is the VCL thread-safe or not?"

The answer to this question seems not to be an easy one. Nor does it seem to be an absolute one. On one hand, Borland (and some books) can be heard to claim that the VCL is in fact thread safe. On the other hand are Delphi Experts and other books that claim the opposite. Why do people disagree on this already complex enough topic? To understand this, we have to take a deeper look under the hood of the TThread class in the VCL itself.

TThread
The TThread class lets you create multiple threads of execution in your Delphi application. Like Experts and other abstract classes in Delphi, TThread can't be used as-is. You must derive a new thread class from TThread (for example TDrBobThread) and override methods to use it. One of the methods is the Execute, and the other one is Synchronize.

Execute
Execute is the method where we must put the code for the thread to execute. Returning from Execute terminates the thread, frees the thread's stack, and calls the optional OnTerminate event handler (see the on-line for details). Our Execute method must periodically check the Terminated property; because if this is set to True, we must return immediately (otherwise our thread won't terminate correctly when the Terminate method is called).

Synchronize
The Synchronize procedure has one argument of type TThreadMethod. Synchronize lets you call this method of your thread object to avoid multi-thread conflicts with global VCL components. (VCL components can only be used from the main VCL thread; Synchronize calls the method you specify from within the main VCL thread so you can freely use properties and call methods of VCL components).

Thread-Safe
It is the Synchronized method that is allowed to use the global VCL visual components and call methods of these components. And if we add multiple threads to our application, we can safely access all VCL components, and call their methods as long as we only do so within the method we passed to Synchronize.

So, while we could say that the VCL as-is may not be thread-safe (i.e. you cannot just access VCL components in two threads' Execute method simultaneously), we have just seen a way to make sure we can use the VCL in a thread-safe way, by passing a custom method to the Synchronize procedure and use the global VCL components from there.

So, the VCL itself may not be thread-safe, but we can use it in a thread-safe way!

See the DEMOS/THREADS directory in your Delphi 2 installation for a nice multi-threaded sorting example (bubble sort vs. selection sort vs. quick sort), and notice how the VCL components are only used from within the Synchronized method DoVisualSwap.


For those of you who want to see the main SORTTHDS.PAS file, here it is:
  unit SortThds;
  interface
  uses
    Classes, Graphics, ExtCtrls;

  Type
  { TSortThread }

    PSortArray = ^TSortArray;
    TSortArray = array[0..MaxInt div SizeOf(Integer) - 1] of Integer;

    TSortThread = class(TThread)
    private
      FBox: TPaintBox;
      FSortArray: PSortArray;
      FSize: Integer;
      FA, FB, FI, FJ: Integer;
      procedure DoVisualSwap;
    protected
      procedure Execute; override;
      procedure VisualSwap(A, B, I, J: Integer);
      procedure Sort(var A: array of Integer); virtual; abstract;
    public
      constructor Create(Box: TPaintBox; var SortArray: array of Integer);
    end;

  { TBubbleSort }

    TBubbleSort = class(TSortThread)
    protected
      procedure Sort(var A: array of Integer); override;
    end;

  { TSelectionSort }

    TSelectionSort = class(TSortThread)
    protected
      procedure Sort(var A: array of Integer); override;
    end;

  { TQuickSort }

    TQuickSort = class(TSortThread)
    protected
      procedure Sort(var A: array of Integer); override;
    end;

  procedure PaintLine(Canvas: TCanvas; I, Len: Integer);

  implementation

  procedure PaintLine(Canvas: TCanvas; I, Len: Integer);
  begin
    Canvas.PolyLine([Point(0, I * 2 + 1), Point(Len, I * 2 + 1)]);
  end;

  { TSortThread }

  constructor TSortThread.Create(Box: TPaintBox; var SortArray: array of Integer);
  begin
    inherited Create(False);
    FBox := Box;
    FSortArray := @SortArray;
    FSize := High(SortArray) - Low(SortArray) + 1;
    FreeOnTerminate := True;
  end;

  { Since DoVisualSwap uses a VCL component (i.e., the TPaintBox) it should never
    be called directly by this thread.  DoVisualSwap should be called by passing
    it to the Synchronize method which causes DoVisualSwap to be executed by the
    main VCL thread, avoiding multi-thread conflicts. See VisualSwap for an
    example of calling Synchronize. }

  procedure TSortThread.DoVisualSwap;
  begin
    with FBox do
    begin
      Canvas.Pen.Color := clBtnFace;
      PaintLine(Canvas, FI, FA);
      PaintLine(Canvas, FJ, FB);
      Canvas.Pen.Color := clRed;
      PaintLine(Canvas, FI, FB);
      PaintLine(Canvas, FJ, FA);
    end;
  end;

  { VisusalSwap is a wrapper on DoVisualSwap making it easier to use.  The
    parameters are copied to instance variables so they are accessable
    by the main VCL thread when it executes DoVisualSwap }

  procedure TSortThread.VisualSwap(A, B, I, J: Integer);
  begin
    FA := A;
    FB := B;
    FI := I;
    FJ := J;
    Synchronize(DoVisualSwap);
  end;

  { The Execute method is called when the thread starts }

  procedure TSortThread.Execute;
  begin
    Sort(Slice(FSortArray^, FSize));
  end;

  { TBubbleSort }

  procedure TBubbleSort.Sort(var A: array of Integer);
  var
    I, J, T: Integer;
  begin
    for I := High(A) downto Low(A) do
      for J := Low(A) to High(A) - 1 do
        if A[J] > A[J + 1] then
        begin
          VisualSwap(A[J], A[J + 1], J, J + 1);
          T := A[J];
          A[J] := A[J + 1];
          A[J + 1] := T;
          if Terminated then Exit;
        end;
  end;

  { TSelectionSort }

  procedure TSelectionSort.Sort(var A: array of Integer);
  var
    I, J, T: Integer;
  begin
    for I := Low(A) to High(A) - 1 do
      for J := High(A) downto I + 1 do
        if A[I] > A[J] then
        begin
          VisualSwap(A[I], A[J], I, J);
          T := A[I];
          A[I] := A[J];
          A[J] := T;
          if Terminated then Exit;
        end;
  end;

  { TQuickSort }

  procedure TQuickSort.Sort(var A: array of Integer);

    procedure QuickSort(var A: array of Integer; iLo, iHi: Integer);
    var
      Lo, Hi, Mid, T: Integer;
    begin
      Lo := iLo;
      Hi := iHi;
      Mid := A[(Lo + Hi) div 2];
      repeat
        while A[Lo] < mid do inc(lo);
        while a[hi] > Mid do Dec(Hi);
        if Lo <= hi then
        begin
          VisualSwap(A[Lo], A[Hi], Lo, Hi);
          T := A[Lo];
          A[Lo] := A[Hi];
          A[Hi] := T;
          Inc(Lo);
          Dec(Hi);
        end;
      until Lo > Hi;
      if Hi > iLo then QuickSort(A, iLo, Hi);
      if Lo < ihi then QuickSort(A, Lo, iHi);
      if Terminated then Exit;
    end;

  begin
    QuickSort(A, Low(A), High(A));
  end;

  end.


This webpage © 1996-2017 by Bob Swart (aka Dr.Bob - www.drbob42.com). All Rights Reserved.