优雅地终止所有线程

时间:2017-11-22 07:56:44

标签: multithreading delphi delphi-10.2-tokyo

我在我的一个解决方案中使用this

我的要求是清除队列并在单击“停止”按钮时优雅地终止所有线程。

为此,我创建了一个ObjectList

var
  List: TObjectList<TMyConsumerItem>;
begin
  { Create a new List. }
  List := TObjectList<TMyConsumerItem>.Create();

后来我做了这个修改:

procedure TForm1.DoSomeJob(myListItems: TStringList);
...
for i := 1 to cThreadCount do
    List.Add(TMyConsumerItem.Create(aQueue, aCounter));

在停止按钮按钮上单击我正在执行此操作

for i := 0 to List.Count - 1 do
  begin
    List.Item[i].Terminate;
  end;
  aCounter.Free;
  aQueue.Free;

在执行此操作时,我的应用程序正在被绞死。这是正确的方法还是我错过了什么?

我使用的是10.2东京

修改1:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type

  TMyConsumerItem = class(TThread)
  private
    FQueue : TThreadedQueue<TProc>;
    FSignal : TCountDownEvent; 
  protected
    procedure Execute; override;
  public
    constructor Create( aQueue : TThreadedQueue<TProc>; aSignal : TCountdownEvent);
  end;


  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure StopClick(Sender: TObject);
  private
    { Private declarations }
    List: TObjectList<TMyConsumerItem>;
    aQueue: TThreadedQueue<TProc>;
    aCounter: TCountDownEvent;
    procedure DoSomeJob( myListItems : TStringList);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  SyncObjs, Generics.Collections;

{- Include TMyConsumerItem class here }

procedure TForm1.Button1Click(Sender: TObject);
var
  aList : TStringList;
  i : Integer;
begin
  aList := TStringList.Create;
  Screen.Cursor := crHourGlass;
  try
    for i := 1 to 20 do aList.Add(IntToStr(i));
    DoSomeJob(aList);
  finally
    aList.Free;
    Screen.Cursor := crDefault;
  end;
end;

procedure TForm1.StopClick(Sender: TObject);
begin
  for i := 0 to List.Count - 1 do
  begin
    List.Item[i].Terminate;
  end;
  List.Free;
  aCounter.WaitFor;
  aCounter.Free;
  aQueue.Free;
end;

procedure TForm1.DoSomeJob(myListItems: TStringList);
const
  cThreadCount = 10;
  cMyQueueDepth = 100;
var
  i: Integer;

  function CaptureJob(const aString: string): TProc;
  begin
    Result :=
      procedure
      var
        i,j : Integer;
      begin
        // Do some job with aString
        for i := 0 to 1000000 do
          j := i;
        // Report status to main thread
        TThread.Synchronize(nil,
          procedure
          begin
            Memo1.Lines.Add('Job with:'+aString+' done.');
          end
        );

      end;
  end;
var
  aThread : TThread;
begin
  List := TObjectList<TMyConsumerItem>.Create();
  List.OwnsObjects := False;
  aQueue := TThreadedQueue<TProc>.Create(cMyQueueDepth);
  aCounter := TCountDownEvent.Create(cThreadCount);
  try
    for i := 1 to cThreadCount do
       List.Add(TMyConsumerItem.Create(aQueue, aCounter));
    for i := 0 to myListItems.Count - 1 do
    begin
      aQueue.PushItem(CaptureJob(myListItems[i]));
    end;
  finally

  end;
end;


constructor TMyConsumerItem.Create(aQueue: TThreadedQueue<TProc>; aSignal : TCountDownEvent);
begin
 Inherited Create(false);
 Self.FreeOnTerminate := true;
 FQueue := aQueue;
 FSignal := aSignal;
end;

procedure TMyConsumerItem.Execute;
var
aProc : TProc;
begin
 try
 repeat
  FQueue.PopItem(aProc);
  aProc();
 until Terminated;
 finally
  FSignal.Signal;
 end;
end;
end.

2 个答案:

答案 0 :(得分:1)

Terminate仅将Terminated属性设置为true。重要的是线程的内部循环定期检查Terminated属性,并在设置为true时从Execute方法返回。之后,在释放队列或线程池对象之前,在主线程中使用WaitFor检查线程是否已全部结束。

答案 1 :(得分:1)

您遗漏了一些关于作业队列如何工作以及如何与线程池交互的重要内容。

  1. 引用自行终止的线程是错误的。删除List,因为它没用。
  2. 要在稍后完成队列,请将aQueue设为全局。
  3. 要完成线程池,请为队列添加与线程一样多的空任务。
  4. 请参阅下面的示例,了解如何实施停止方法。请注意,aCounteraQueue都必须是全局范围的。免责声明未经测试,目前不在编译器前面。
  5. 如果您需要在作业任务中中止正在进行的工作,则必须提供对每个作业任务的全局(范围内)标记的引用,并发出信号以结束任务。
  6. 还有其他图书馆可以执行类似的工作,请参阅Delphi PPL或经过充分验证的OTL library
  7. procedure TForm1.StopClick(Sender: TObject);
    var
      i : Integer;
      aThread : TThread;
    begin
      // Kill the worker threads by pushing nil
      for i := 1 to cThreadCount do
        aQueue.PushItem(nil);
    
      // Since the worker threads synchronizes with the main thread,
      // we must wait for them in another thread.
      aThread := TThread.CreateAnonymousThread(
        procedure
        begin
          aCounter.WaitFor; // Wait for threads to finish
          aCounter.Free;
          aQueue.Free;
        end
      );
      aThread.FreeOnTerminate := false;
      aThread.Start;
      aThread.WaitFor;  // Safe to wait for the anonymous thread
      aThread.Free;
    end;