You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi 10.4中TTask与TThreadPool任务报告重复问题问询

Delphi 10.4中TTask结合TThreadPool任务日志重复问题解决

问题场景

在Delphi 10.4(Sydney)中使用TTask实现并发任务,通过TThreadPool限制最大并发线程数为4,运行后发现部分任务的启动和完成日志重复出现,具体表现:

  • TaskId 1、3、7、8的「Started TaskId」日志重复两次
  • TaskId 3、8的「TaskId completed」日志重复两次

原代码

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.

运行输出

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.

问题分析

核心原因是控制台输出WriteLn不是线程安全的:当多个线程同时调用WriteLn写入控制台时,操作系统的控制台输出缓冲区会出现竞态条件,导致输出内容重叠、重复或截断,看起来像是任务被重复执行,但实际上每个任务只执行了一次。

同时需确认闭包逻辑无问题:代码中ProcessTask的参数为传值类型,每个匿名函数持有独立的TaskId,不存在闭包捕获循环变量的陷阱,输出混乱是导致误解的直接原因。

解决方案

添加临界区(TCriticalSection)保护控制台输出操作,确保同一时间只有一个线程写入控制台,消除输出混乱:

var
  MyThreadPool: TThreadPool;
  I: Integer;
  Tasks: TArray<ITask>;
  OutputLock: TCriticalSection; // 用于同步控制台输出的临界区

function ProcessTask(const TaskId: Integer): TProc;
begin
  Result := procedure
  Begin
    // 同步启动日志输出
    OutputLock.Enter;
    try
      WriteLn(Format('Started TaskId: %d ThreadId: %d',[TaskId,TThread.Current.ThreadID]));
    finally
      OutputLock.Leave;
    end;

    Sleep(2000); // 模拟业务逻辑

    // 同步完成日志输出
    OutputLock.Enter;
    try
      WriteLn(format('TaskId %d completed. ThreadId: %d',[TaskId, TThread.Current.ThreadID]));
    finally
      OutputLock.Leave;
    end;
  End;
end;

begin
  OutputLock := TCriticalSection.Create;
  try
    Writeln('Creating TThreadPool');
    MyThreadPool := TThreadPool.Create;

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

      SetLength(Tasks,10);

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

      // 等待所有任务完成
      for I := 0 to High(Tasks) do
        Tasks[I].Wait;

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

    finally
      MyThreadPool.Free;
    end;
  except
    on E: Exception do
    begin
      OutputLock.Enter;
      try
        Writeln(E.ClassName, ': ', E.Message);
      finally
        OutputLock.Leave;
      end;
    end;
  end;
  OutputLock.Free;
end.

验证效果

添加临界区后,控制台输出会变得有序,每个任务的启动和完成日志只会出现一次,能准确反映任务的执行情况。

内容的提问来源于stack exchange,提问作者Shweta Softwares

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 05:15:55