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

时间:2012-12-16 09:09:40

标签: multithreading delphi 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.

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

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;

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

2 个答案:

答案 0 :(得分:6)

限制

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

只要您想将引用用于其他任何事情(如暂停和恢复)或者可以从主线程以外的任何线程的上下文中访问它们,您真的应该深入研究各种线程机制程序执行。

一些关键字:线程同步,事件信令,互斥锁,信号量,关键部分。

使用Delphi进行多线程编程的一个很好的参考是Multithreading - The Delphi Way

答案

如果您确实需要稍后引用线程,则需要将引用存储在某处。但是您不需要为您创建的每个线程都有单独的变量。有很多选择。我想到了一个数组,但最简单的可能是一个对象列表(来自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;

TObjectList具有OwnsObjects属性,默认情况下为True。这意味着释放对象列表也将释放它包含的实例。这意味着您需要确保列表仅包含有效的引用。通常这不是问题,但是对于线程,您需要特别小心,特别是当您将FreeOnTerminate设置为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;

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

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

要自动处理锁定内容,您可以使用 TThreadList (来自classes单位)。所需的代码更改:

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

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

因为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;

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

procedure TForm1.HandleThreadTerminate(Sender: TObject);
begin
  MyThreads.Remove(idx);
end;

编辑:正如@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;

答案 1 :(得分:1)

答案非常简单 - 如果您创建一个FreeTnTerminate类型TThread,那么您根本不会对该TThread实例使用任何外部引用。这样做就像玩俄罗斯轮盘赌......它迟早会在你的脸上爆炸。是的,有一些方法可以做到,但你不应该这样做。

您可以做的是在父母和孩子之间引入共享通信媒介。从子线程传递状态更新的方法有很多种,不涉及对TThread实例的任何直接引用。 (查看自定义窗口消息,可能是最常用的方法。)

一个非常明显的(我认为),简单的例子。我正在这个消息框中编码,所以不要指望它编译...这只是为了帮助证明需要一个单独的机制,需要配置,这样你就可以安全地在父和子之间进行通信而不依赖于引用一些TThread实例。 (这假设有一个子线程......是的,还有很多其他方法可以做到这一点。)

unit MyPubComm.pas;

initialization
uses ...;

type
  TThreadComm = class
  private
    fThreadIsBusy:Boolean;  //more likely a State of some sort
    fThreadMessage:String;
  public
   property ThreadIsBusy:Boolean read fThreadIsBusy write fThreadIsBusy;
   property ThreadMessage:String read fThreadMessage write fThreadMessage;
  end;

  function IsChildBusy:Boolean;
  function ChildMessage:String;
  procedure SetThreadMessage(const MessageText:String);

var
  pubThreadStatus:TThreadComm;


implementation

function IsChildBusy:Boolean;
begin
  pubThreadStatus.Monitor.Enter;
  try
    Result := pubThreadStatus.ThreadIsBusy;
  finally
    pubThreadStatus.Monitor.Exit;
  end;
end;

function ChildMessage:String;
begin
  pubThreadStatus.Monitor.Enter;
  try
    Result := pubThreadStatus.ThreadMessage;
  finally
    pubThreadStatus.Monitor.Exit;
  end;
end;

procedure SetThreadMessage(const MessageText:String);
begin
  pubThreadStatus.Monitor.Enter;
  try
    pubThreadStatus.ThreadMessage := MessageText;
  finally
    pubThreadStatus.Monitor.Exit;
  end;
end;


initialization
  pubThreadStatus := TThreadCom.Create;
finalization
  pubThreadStatus.Free();

--------
unit MainAppCode.pas
...
implementation
Uses MyPubComm;

procedure TMyForm1.WorkingOnSomething();
begin
   if IsChildBusy() then  //safely check child state
   begin 
      label1.caption := 'Child thread busy...';
   end
   else
   begin
      label1.caption := ChildMessage();  //safely retrieve shared data
   end;
end;
------
MyThread.pas
...
implementation
Uses MyPubComm;

function TMyThread.WorkerBee();
begin
   If TimeToCommunicateThreadMessage then
   begin 
     SetThreadMessage(MyMessage);  //safely update shared data
   end;
end;

这也应该在WorkingOnSomething()中显示一个明显的问题。在检查IsChildBusy()和ChildMessage()调用之间,实际繁忙状态和消息文本可能会改变,这就是为什么需要更强大的通信方法