自动或手动释放TThread

时间:2010-08-24 13:47:13

标签: multithreading delphi delphi-7 tthread

我的程序中有一个主线程和一个单独的线程。如果单独的线程在主线程之前完成,它应该自动释放。如果主线程首先完成,它应该释放单独的线程。

我知道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;

5 个答案:

答案 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。如果您的线程提前完成,请按照通常的方式清除线程已使用的本地资源,然后在应用程序完成时让主线程释放子线程。您将获得两全其美 - 尽可能快地由子线程释放的资源,并且不用担心线程同步。 (而且它在设计/代码/理解/支持方面更加简单,还有额外的好处......)