我有一个来自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(如第一个代码示例中)执行此操作?
答案 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()调用之间,实际繁忙状态和消息文本可能会改变,这就是为什么需要更强大的通信方法