multithreadingdelphithreadpool

How can I use TTask and TThreadPool in Delphi to run multiple tasks and ensure that its start and completion are reported accurately?


I am trying to implement multithreading in Delphi 10.4 (Sydney). I am using TTask to run tasks concurrently and TThreadPool to limit the simultaneously running threads.

Here's my code:

var
  MyThreadPool: TThreadPool;
  I: Integer;
  Tasks: TArray<ITask>;

function ProcessTask(const TaskId: Integer): TProc;
begin
  Result := procedure
  Begin
    WriteLn(Format('Started TaskId: %d ThreadId: %d',[TaskId,TThread.Current.ThreadID]));
    Sleep(2000); // Simulate work
    WriteLn(format('TaskId %d completed. ThreadId: %d',[TaskId, TThread.Current.ThreadID]));
  End;
end;

begin
  try

    Writeln('Creating TThreadPool');
    MyThreadPool := TThreadPool.Create;

    try
      MyThreadPool.SetMinWorkerThreads(1);
      MyThreadPool.SetMaxWorkerThreads(4);

      // Hold 10 tasks
      SetLength(Tasks,10);

      for I := 0 to High(Tasks) do
      Begin
        Tasks[I] := TTask.Create(
          ProcessTask(I+1),
          MyThreadPool
        );

        // Start the task
        Tasks[I].Start;
      End;

      // Wait for all tasks to complete
      for I := 0 to High(Tasks) do
        Tasks[I].Wait;

      WriteLn('All tasks completed.');
      ReadLn;

    finally
      MyThreadPool.Free;
    end;

  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

The output of the above code is:

Creating TThreadPool
Started TaskId: 1 ThreadId: 17292
Started TaskId: 1 ThreadId: 17292
Started TaskId: 2 ThreadId: 19256
Started TaskId: 3 ThreadId: 25268
Started TaskId: 3 ThreadId: 25268
Started TaskId: 4 ThreadId: 21112
TaskId 1 completed. ThreadId: 17292
Started TaskId: 5 ThreadId: 17292
TaskId 2 completed. ThreadId: 19256
Started TaskId: 6 ThreadId: 19256
TaskId 3 completed. ThreadId: 25268
TaskId 3 completed. ThreadId: 25268
TaskId 4 completed. ThreadId: 21112
Started TaskId: 7 ThreadId: 21112Started TaskId: 8 ThreadId: 25268Started TaskId: 7 ThreadId: 21112Started TaskId: 8 ThreadId: 25268
TaskId 5 completed. ThreadId: 17292
Started TaskId: 9 ThreadId: 17292
TaskId 6 completed. ThreadId: 19256
Started TaskId: 10 ThreadId: 19256
TaskId 8 completed. ThreadId: 25268
TaskId 8 completed. ThreadId: 25268
TaskId 7 completed. ThreadId: 21112
TaskId 9 completed. ThreadId: 17292
TaskId 10 completed. ThreadId: 19256
All tasks completed.

The problem with the code is that the Start and Completion reporting is duplicated in certain cases:

  1. The Started TaskId appears twice for TaskId 1,3,7,8.
  2. The TaskId completed appears twice for TaskId 3,8.

Solution

  • Without looking too deeply, the problem is likely that you are using WriteLn in multi-threaded code which can be prone to problems. Add some sort of synchronization to your output to ensure only one WriteLn call is executed at a time.

    var
      ConsoleLock: TCriticalSection;
    
    procedure SafeWriteLn(const Msg: string);
    begin
      ConsoleLock.Enter;
      try
        WriteLn(Msg);
      finally
        ConsoleLock.Leave;
      end;
    end;
    
    ConsoleLock := TCriticalSection.Create;
    try
      //your stuff
    finally
      ConsoleLock.Free;
    end;