9

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

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

5 回答 5

7

您可以将其简化为:

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- 如果是,您的线程已完成执行。

顺便说一句:不要使用OnClose事件,OnDestroy而是使用。前者不一定被调用,在这种情况下,您的线程可能会继续运行并保持您的进程处于活动状态。

于 2010-08-24T14:34:43.303 回答
5

让主线程为工作线程的 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;
于 2010-08-24T17:06:03.480 回答
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 对象。为了安全起见,成本并不高。

于 2010-08-24T14:17:19.327 回答
0

我会说根本不推荐混合模型。您要么使用 FreeOnTerminate 并且不再接触线程,要么不使用。否则,您需要一种受保护的方式让两者进行通信。

由于您希望对线程变量进行精细控制,因此不要使用 FreeOnTerminate。如果您的线程提前结束,请像往常一样清除线程已消耗的本地资源,然后在应用程序结束时让主线程释放子线程。您将获得两全其美 - 子线程尽快释放资源,而不必担心线程同步。(而且它的额外好处是设计/代码/理解/支持更简单......)

于 2010-08-25T00:56:42.697 回答
0

老实说,你的


... Do some processing

真正的问题在这里。这是递归做某事的循环吗?如果不是,相反,这是一项艰巨的任务,您应该考虑将此任务拆分为小过程/函数,并将所有内容放在执行主体中,使用条件 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;

它很脏,几乎是 hack,但会按预期工作。

关于 FreeOnTerminate,嗯......只需删除声明并始终


FreeAndNil(ThreadObject);

我不是同步的粉丝。我喜欢更关键的部分,因为可以灵活地扩展代码以处理更多共享数据。

在表单公共部分,声明:

ControlSection : TRTLCriticalSection;

在表单 create 或 thread.create 之前的其他地方,

InitializeCriticalSection(ControlSection);

然后,每次写入共享资源(包括 MyThreadReady 变量)时,执行


EnterCriticalSection ( ControlSection );
  MyThreadReady := True; //or false, or whatever else
LeaveCriticalSection ( ControlSection );

在你走(退出)之前,打电话给


DeleteCriticalSection ( ControlSection );

并像往常一样释放线程。

问候拉斐尔

于 2010-08-24T22:33:36.857 回答