我的程序中有一个主线程和一个单独的线程。如果单独的线程在主线程之前完成,它应该自动释放。如果主线程首先完成,它应该释放单独的线程。
我知道FreeOnTerminate,我读过你必须小心使用它。
我的问题是,以下代码是否正确?
procedure TMyThread.Execute;
begin
... Do some processing
Synchronize(ThreadFinished);
if Terminated then exit;
FreeOnTerminate := true;
end;
procedure TMyThread.ThreadFinished;
begin
MainForm.MyThreadReady := true;
end;
procedure TMainForm.Create;
begin
MyThreadReady := false;
MyThread := TMyThread.Create(false);
end;
procedure TMainForm.Close;
begin
if not MyThreadReady then
begin
MyThread.Terminate;
MyThread.WaitFor;
MyThread.Free;
end;
end;
答案 0 :(得分:8)
您可以将其简化为:
procedure TMyThread.Execute;
begin
// ... Do some processing
end;
procedure TMainForm.Create;
begin
MyThread := TMyThread.Create(false);
end;
procedure TMainForm.Close;
begin
if Assigned(MyThread) then
MyThread.Terminate;
MyThread.Free;
end;
说明:
使用FreeOnTerminate
或手动释放线程,但绝不要同时使用。线程执行的异步性质意味着您存在不释放线程的风险或(更糟糕的是)执行两次。在完成执行后保持线程对象没有风险,并且在已经完成任务的线程上调用Terminate()
没有风险。
无需同步对仅从一个线程写入并从另一个线程读取的布尔值的访问。在最坏的情况下,您得到了错误的值,但由于异步执行无论如何都是虚假的影响。仅对于无法以原子方式读取或写入的数据,才需要进行同步。如果您需要同步,请不要使用Synchronize()
。
没有必要使用类似于MyThreadReady
的变量,因为您可以使用WaitForSingleObject()
来查询线程的状态。将MyThread.Handle
作为第一个参数并将0
作为第二个参数传递给它,并检查结果是否为WAIT_OBJECT_0
- 如果是,则表示您的线程已完成执行。
BTW:请勿使用OnClose
事件,而是使用OnDestroy
。前者不一定被调用,在这种情况下,您的线程可能会继续运行并使您的进程保持活动状态。
答案 1 :(得分:4)
让主线程为工作线程的OnTerminate事件分配处理程序。如果工作线程首先完成,则处理程序可以通知主线程释放线程。如果主线程首先完成,它可以终止工作线程。例如:
procedure TMyThread.Execute;
begin
... Do some processing ...
end;
procedure TMainForm.Create;
begin
MyThread := TMyThread.Create(True);
MyThread.OnTerminate := ThreadFinished;
MyThread.Resume; // or MyThread.Start; in D2010+
end;
const
APPWM_FREE_THREAD = WM_APP+1;
procedure TMainForm.ThreadFinished(Sender: TObject);
begin
PostMessage(Handle, APPWM_FREE_THREAD, 0, 0);
end;
procedure TMainForm.WndProc(var Message: TMessage);
begin
if Message.Msg = APPWM_FREE_THREAD then
StopWorkerThread
else
inherited;
end;
procedure TMainForm.StopWorkerThread;
begin
if MyThread <> nil then
begin
MyThread.Terminate;
MyThread.WaitFor;
FreeAndNil(MyThread);
end;
end;
procedure TMainForm.Close;
begin
StopWorkerThread;
end;
答案 2 :(得分:2)
不,你的代码不好(虽然它可能会在99.99%甚至100%的情况下工作)。如果你打算从主线程终止工作线程,不要将FreeOnTerminate设置为True(我没有看到你想通过将FreeOnTerminate设置为True在上面的代码中获得什么,它至少使你的代码不易理解)
终止工作线程的一个更重要的情况是,您正在尝试在工作线程处于等待状态时关闭应用程序。如果你只是调用Terminate,线程将不被唤醒,通常你应该使用额外的同步对象(通常是事件)来唤醒工作线程。
还有一句话 - 没有必要
begin
MyThread.Terminate;
MyThread.WaitFor;
MyThread.Free;
end;
如果查看TThread.Destroy代码,它会调用Terminate和WaitFor,所以
MyThread.Free;
就足够了(至少在Delphi 2009中,手头没有Delphi 7来源检查)。
<强>更新强>
阅读mghie的答案。请考虑以下情况(在1 CPU系统上更好):
主线程正在执行
procedure TMainForm.Close;
begin
if not MyThreadReady then
begin
MyThread.Terminate;
MyThread.WaitFor;
MyThread.Free;
end;
end;
它检查了MyThreadReady值(它是False)并被调度程序关闭。
现在调度程序切换到工作线程;它执行
Synchronize(ThreadFinished);
并强制调度程序切换回主线程。主线程继续执行:
MyThread.Terminate; // no problem
MyThread.WaitFor; // ???
MyThread.Free;
你能说出WaitFor会发生什么吗?我不能(需要深入了解TThread来源回答,但乍一看看起来像死锁)。
你真正的错误是不同的 - 你编写了一个不可靠的代码并试图找出它是否正确。这对于线程来说是不好的做法 - 你应该学会编写一个可靠的代码。
至于资源 - 当终止TThread(使用FreeOnTerminate = False)时,剩下的唯一资源是Windows线程句柄(在线程终止后它不使用大量的Windows资源)和内存中的Delphi TThread对象。安全方面的成本并不高。
答案 3 :(得分:0)
老实说,你的
... Do some processing
这是真正的问题吗?这是递归递送的循环吗?如果没有,相反,这是一个巨大的任务,你应该考虑在小程序/函数中拆分这个任务,并将所有的一起放在执行体中,用条件一个接一个地调用if if来知道线程状态,如:
While not Terminated do
begin
if MyThreadReady then
DoStepOneToTaskCompletion
else
clean_and_or_rollback(Something Initialized?);
if MyThreadReady then
DoStepTwoToTaskCompletion
else
clean_and_or_rollback(Something Initialized?, StepOne);
if MyThreadReady then
DoStepThreeToTaskCompletion
else
clean_and_or_rollback(Something Initialized?, StepOne, StepTwo);
Self.DoTerminate; // Not sure what to expect from that one
end;
它很脏,几乎是黑客,但会按预期工作。
关于FreeOnTerminate,嗯......只需删除声明并始终
FreeAndNil(ThreadObject);
我不是同步的粉丝。我喜欢更关键的部分,以便灵活地扩展代码以处理更多的共享数据。
在表单公共部分,声明:
ControlSection : TRTLCriticalSection;
在form create或thread.create之前的其他地方,
InitializeCriticalSection(ControlSection);
然后,每次写入共享资源(包括MyThreadReady变量)时,请执行
EnterCriticalSection ( ControlSection );
MyThreadReady := True; //or false, or whatever else
LeaveCriticalSection ( ControlSection );
出发前(退出),请致电
DeleteCriticalSection ( ControlSection );
并像往常一样释放你的主题。
此致 圣拉斐尔
答案 4 :(得分:0)
我会说不建议使用混合模型。您要么使用FreeOnTerminate又不要再次触摸该线程,要么您没有。否则,您需要一种受保护的方式让两者进行沟通。
由于您希望对线程变量进行精细控制,因此请勿使用FreeOnTerminate。如果您的线程提前完成,请按照通常的方式清除线程已使用的本地资源,然后在应用程序完成时让主线程释放子线程。您将获得两全其美 - 尽可能快地由子线程释放的资源,并且不用担心线程同步。 (而且它在设计/代码/理解/支持方面更加简单,还有额外的好处......)