Search code examples
multithreadingdelphilazarusfreepascal

How Do I Call a Thread.Execute in GUI Thread?


There is a TThread descendant class with its own Execute method doing some math. It works fine but I am looking for the optimization of the following kind. The GUI thread and context of the program determine the count of necessary instances of these threads to be created, run and freed. In certain (rare or user determined) circumstances creation of one instance is enough.

By the moment I use the following construction (in pseudocode):

if ThreadsCount>1 then
  begin
    Creation of threads
    starting them
    waiting for results
    evaluating and assigning the best result
    freeing the threads
  end
else
  starting the math procedure (edited here separately)

// and in MyThread class declaration
procedure Execute... (edited here separately)

So there are two places in code that have my math procedure and I have to edit both of them if some math changes are applied. The GUI math procedure is a bit different from that one called in thread so I can not simply extract the method and call it.

I wonder if there is a way to create a thread instance and call its Execute method in GUI thread?


Solution

  • You could write some seriously hacky, indescribably bad code to enable you to safely call a TThread's Execute(). But it's an absurd thing to do. The whole point of the TThread class is that it:

    • starts a new thread in the OS;
    • then calls Execute() on that thread.

    So:

    1. If you don't need a thread, there's absolutely no point in starting a thread that you don't want to use.
    2. You would need to prevent Execute() from doing any processing on its thread-run.
    3. You could then call Execute from the main thread.
    4. But since you have no guarantees how long the thread will take to not do any processing when it calls Execute(), you'd still need to wait for the thread to finish before you can destroy the TThread object.

    The GUI math procedure is a bit different from that one called in thread so I can not simply extract the method and call it.

    This makes absolutely no sense.

    If your two "math procedures" are different, then trying to call the thread-implementation from GUI would change the behaviour of your program. Conversely, if you can reuse the thread-implementation, then you most certainly can also extract the method! (Or at the very least the common elements.)


    Caution

    That said, there is some caution required when sharing code that might run in a TThread.Execute(). Any code that must run on the main thread needs to be synchronised or queued. Inside TThread objects, you'd simply call the Synchronize() or Queue() method. However, shared code shouldn't be on a TThread object making things a little trickier.

    To resolve this, you can use the the Synchronize() and Queue() class methods on TThread. This allows you to synchronise without instantiating a TThread instance. (Note these methods are safe to call from the main thread because they would simply call the sync method directly in that case.)

    Code along the following lines should do the trick.

    Implement your shared code in a suitable object. This is conceptually a runnable object, and something you may want to research.

    TSharedProcess = class
    private
      { Set this if the process is run from a child thread,
        leave nil if run from main thread. }
      FThread: TThread;
      procedure SyncProc();
    public
      procedure Run();
      property Thread: TThread read FThread write FThread;
    end;
    
    procedure TSharedProcess.Run();
    begin
      ...
      TThread.Synchronize(FThread, SyncProc);
      ...
    end;
    

    When you want to run the shared code from the main thread, the following is an option.

    begin
      LProc := TSharedProcess.Create(...);
      try
        LProc.Run();
      finally
        LProc.Free;
      end;
    end;
    

    To run from a child thread a simple thread wrapper will suffice. And then you can create the runnable object in the main thread, and pass it to the thread wrapper.

    { TShardProcessThread for use when calling from child thread. }
    
    constructor TSharedProcessThread.Create(AProc: TSharedProcessThread);
    begin
      FProc := AProc;
      FProc.Thread := Self;
      inherited;
    end;
    
    procedure TShardProcessThread.Execute();
    begin
      FProc.Run();
    end;
    
    { Main thread creates child thread }
    begin
      { Keep reference to FProc because it can only be destroyed after
        thread terminates. 
        TIP: Safest would be to use a reference counted interface. }
      FProc := TSharedProcess.Create(...);
      try
        LThread := TShardProcessThread.Create(FProc);
        LThread.OnTerminate := HandleThreadTerminate;
      except
        { Exception in thread create means thread will not run and
         will not terminate; so free object immediately. }
        FProc.Free;
        raise;
      end;
    end;
    

    Disclaimer

    I have not tested this code because I see no benefit in doing something like this. Users gain nothing by being able to force code to run on the main thread. Furthermore the paradigms for synchronous code are fundamentally different to asynchronous code. Trying to implement a hybrid reduces maintainability by cluttering your 'business code' with technical detail.

    Use at your own risk.