Delphi:如何处理动态创建的线程

Red*_*ber 1 delphi multithreading delphi-xe2

我有一个来自TThread的孩子.一切正常,但我怎么能大规模暂停或恢复我创建的线程?或者我怎么才能暂停第二个线程(在Button2Click中创建)?这是我的代码的一部分:

TMyThread = class(TThread)
private
  source_file, destination_file: string;
  total_size, current_size, download_item_id: integer;
protected
  procedure ShowResult;
  procedure Execute; override;
public
end;

var
 MyThread: TMyThread;

begin

procedure TMyThread.Execute;
 begin
  //Some code for download file here, it doesn't matter
 end;


procedure TForm1.Button1Click(Sender: TObject);
begin
  MyThread := TMyThread.Create(True);
  MyThread.source_file :='http://example.com/download1.zip';
  MyThread.destination_file := 'c:\download1.zip';
  MyThread.download_item_id := 0;
  MyThread.Priority := tpNormal;
  MyThread.FreeOnTerminate := True;
  MyThread.Resume;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  MyThread := TMyThread.Create(True);
  MyThread.source_file :='http://example.com/download2.zip';
  MyThread.destination_file := 'c:\download2.zip';
  MyThread.download_item_id := 1;
  MyThread.Priority := tpNormal;
  MyThread.FreeOnTerminate := True;
  MyThread.Resume;
end;

end.
Run Code Online (Sandbox Code Playgroud)

也就是说,如果我创建这样的线程 - 它对我有用:

var
 MyThread1, MyThread2: TMyThread;
...
procedure TForm1.Button1Click(Sender: TObject);
begin
  MyThread1 := TMyThread.Create(True);
  MyThread1.source_file :='http://example.com/download1.zip';
  MyThread1.destination_file := 'c:\download1.zip';
  MyThread1.download_item_id := 0;
  MyThread1.Priority := tpNormal;
  MyThread1.FreeOnTerminate := True;
  MyThread1.Resume;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  MyThread2 := TMyThread.Create(True);
  MyThread2.source_file :='http://example.com/download2.zip';
  MyThread2.destination_file := 'c:\download2.zip';
  MyThread2.download_item_id := 1;
  MyThread2.Priority := tpNormal;
  MyThread2.FreeOnTerminate := True;
  MyThread2.Resume;
end;

//Terminate all of TMyThread
procedure TForm1.Button3Click(Sender: TObject);
begin
  MyThread1.Terminate;
  MyThread2.Terminate;
  ShowMessage('All downloads were terminated!');
end;

//Terminate ONLY the second of TMyThread
procedure TForm1.Button4Click(Sender: TObject);
begin
  MyThread2.Terminate;
  ShowMessage('The second download was terminated!');
end;
Run Code Online (Sandbox Code Playgroud)

但是如何为一组动态创建的TMyThread(如第一个代码示例中)执行此操作?

Mar*_*ema 7

局限性

你真的,真的不应该保留对终止时设置为空闲的线程的引用.它要求各种各样的问题.下面的代码确实使用FreeOnTerminate设置为True的线程.这只是安全的,因为:1)引用在线程终止时和释放之前被删除; 2)在mainthread的上下文中调用OnTerminate处理程序和/或使用线程安全的TList后代; - 最重要的是 - 3)引用仅用于从主线程的上下文中有选择地取消线程.

一旦你想将引用用于其他任何东西(比如暂停和恢复)或者可以从主线程以外的任何线程的上下文中访问它们,你真的应该深入研究线程程序执行的各种机制.

几个关键字:线程同步,事件信号,互斥锁,信号量,关键部分.

使用Delphi进行多线程编程的一个很好的参考是多线程 - Delphi方式

回答

如果您确实需要稍后引用线程,则需要将引用存储在某处.但是您不需要为您创建的每个线程都有单独的变量.有很多选择.我想到了一个数组,但最简单的可能是一个对象列表(来自Contnrs单元):

TForm1 = class(TForm)
  private
    MyThreads: TObjectList;

constructor TForm1.Create(AOwner: TComponent);
begin
  inherited;
  MyThreads := TObjectList.Create;
end;

destructor TForm1.Destroy;
begin
  MyThreads.Free;
  inherited;
end;
Run Code Online (Sandbox Code Playgroud)

TObjectList有一个OwnsObjects默认属性True.这意味着释放对象列表也将释放它包含的实例.这意味着您需要确保列表仅包含有效的引用.通常这不是问题,但是对于线程,您需要特别小心,特别是当您使用FreeOnTerminateset时True.

像在第一个示例中一样创建线程,并进行一些更改:

使用局部变量(您应该避免使用全局变量,即使它们仅在单元内尽可能可见).

将线程添加到对象列表中.

当您还想将'FreeOnTerminate`设置为True时,设置OnTerminate处理程序以从对象列表中删除实例.

procedure TForm1.Button1Click(Sender: TObject);
var
  Thread: TThread;                                       // Local var
begin
  Thread := TMyThread.Create(True);
  MyThreads.Add(Thread);                                 // Add to list
  Thread.source_file :='http://example.com/download1.zip';
  Thread.destination_file := 'c:\download1.zip';
  Thread.download_item_id := 0;
  Thread.Priority := tpNormal;
  Thread.OnTerminate := HandleThreadTerminate;           // Ensure you keep list valid
  Thread.FreeOnTerminate := True;                        // Advice against this!
  Thread.Start;
end;

procedure TForm1.HandleThreadTerminate(Sender: TObject);
var
  idx: Integer;
begin
  // Acquire Lock to protect list access from two threads
  idx := MyThreads.IndexOf(Sender);
  if idx > -1 then
    MyThreads.Delete(idx);
  // Release lock
end;
Run Code Online (Sandbox Code Playgroud)

HandleThreadTerminate过程应该使用某种锁,因此两个线程不能同时尝试从列表中删除实例,因为IndexOf和Delete之间的线程切换可能意味着删除了错误的实例.处理程序中的代码可以写成MyThreads.Remove(Sender).虽然这是单个语句,但它不是线程安全的,因为它与我在幕后显示的代码相同.

编辑:实际上,正如@LURD提到的那样,它恰好是线程安全的,因为OnTerminate是在主线程的上下文中调用的.我将离开TThreadList示例,因为如果您需要/碰巧从多个线程访问列表,这是一个很好的方法.

要自动处理锁定内容,可以使用TThreadList(来自classes设备).所需的代码更改:

当然,您需要实例化TThreadList而不是TObjectList.

constructor TForm1.Create(AOwner: TComponent);
begin
  inherited;
  MyThreads := TThreadList.Create;
end;
Run Code Online (Sandbox Code Playgroud)

因为TThreadList没有概念OwnsObjects,你必须自己释放任何剩余的线程:

destructor TForm1.Destroy;
var
  LockedList: TList;
  idx: integer;
begin
  LockedList := MyThreads.LockList;
  try
    for idx := LockedList.Count - 1 downto 0 do
      TObject(MyThreads.Items(idx)).Free;
  finally
    MyThreads.UnlockList;
  end;

  MyThreads.Free;
  inherited;
end;
Run Code Online (Sandbox Code Playgroud)

现在可以安全地简化OnTerminate处理程序,因为TThreadList将处理所需的锁定.

procedure TForm1.HandleThreadTerminate(Sender: TObject);
begin
  MyThreads.Remove(idx);
end;
Run Code Online (Sandbox Code Playgroud)

编辑:正如@mghie提到的那样,当FreeOnTerminate为真时释放一个线程与终止免费的整个想法发生冲突.您可以在释放线程(将终止它)之前将FreeOnTerminate设置为false,或者如果要保留FreeOnTerminate处理,则应断开OnTerminate处理程序,然后使用Terminate告诉线程终止.

destructor TForm1.Destroy;
var
  LockedList: TList;
  idx: integer;
begin
  LockedList := MyThreads.LockList;
  try
    for idx := LockedList.Count - 1 downto 0 do
    begin
      TThread(MyThreads.Items(idx)).OnTerminate := nil;
      TThread(MyThreads.Items(idx)).Terminate;
    end;
  finally
    MyThreads.UnlockList;
  end;

  MyThreads.Free;
  inherited;
end;
Run Code Online (Sandbox Code Playgroud)

  • 还有,暂停的讨论在哪里?我相信@RedOctober不打算使用Suspend方法.我的观点是维护一个线程列表是没有用的.要执行安全暂停,恢复,取消,您需要一个由其线程proc中的所有线程检查的令牌. (2认同)