【问题标题】:Free a TThread either automatically or manually自动或手动释放 TThread
【发布时间】:2010-08-24 13:47:13
【问题描述】:

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

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

【问题讨论】:

    标签: multithreading delphi delphi-7 tthread


    【解决方案1】:

    您可以将其简化为:

    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。前者不一定被调用,在这种情况下,您的线程可能会继续运行并保持您的进程处于活动状态。

    【讨论】:

    • 您好!单独的 FreeOnTerminate 不是一种选择。另一方面,我不想在主程序运行时让线程占用内存。我正在同步布尔值,因为我相信它保证它将在 MainForm.Close 之前或 MainForm.Close 之后执行。所以 MyThread.Terminate 只有在 FreeOnTerminate 为假时才会被调用。我在这里错了吗?
    • 线程完成后会占用多少内存?如果你不能说你没有理由担心。先测量。但是,如果您坚持,那么将您的线程中的消息作为最后一件事发布,并在消息处理程序中释放线程。 Synchronize() 太卑鄙了,甚至无法考虑您的代码是否在所有情况下都可以正常工作。对它说不。
    • 接受这个作为解决方案,因为这是最干净的一个。感谢大家的回答!
    【解决方案2】:

    让主线程为工作线程的 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;
    

    【讨论】:

    • 我不喜欢它。而不是消除问题,而是将其隐藏得更深,因此找出代码可能失败的地方变得越来越困难。
    • 不,我没有隐瞒任何事情。这是解决原始问题的一种更有效的方法,而且效果很好。工作线程根本不应该尝试直接释放自己。由于主线程是启动线程的线程,并且无论如何都需要管理线程,所以应该是释放线程的线程。 TThread 有一个 OnTerminate 事件,专门用于在线程终止时通知其他代码,并完成与原始 Synchronize(ThreadFinished) 调用所做的完全相同的事情... ...
    • ... PostMessage() 技巧只是将线程的实际释放延迟几毫秒,因为它不能在 OnTerminate 事件本身内执行。但是工作线程在终止时仍然被自动释放,只是没有从线程本身内部释放,这是唯一的区别。
    • OnTerminate 代码在主线程上下文中执行(使用同步过程),老问题仍然存在。一个可靠的解决方案应该将消息从工作线程发布到主线程,并避免使用 Syncronize 和 FreeOnTerminate:= True。
    • 在这种情况下,在主线程上下文中运行 OnTerminate 没有任何问题。主线程总是会收到 OnTerminate 通知,要么是因为在工作线程终止时主线程仍在运行,要么是因为主线程先停止并且对 WaitFor() 的调用允许处理线程的 Synchronize()(因为到那时,主消息循环可能已经停止了)。如果主线程正在停止,则发布的窗口消息将被忽略,这很好,因为主线程的关闭无论如何都会释放工作线程...
    【解决方案3】:

    不,您的代码不好(尽管它可能会在 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 对象。为了安全起见,成本并不高。

    【讨论】:

    • 如果我不将 FreeOnTerminate 设置为 true,则在主线程完成之前不会释放线程。几分钟甚至几小时可能会过去,直到单独线程的资源被释放——我不希望这样。我不明白你在答案的第二段中的意思。在哪种情况下上面的代码不起作用?那 00.01% 是多少?
    【解决方案4】:

    老实说,你的

    
    ... 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;
    

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

    InitializeCriticalSection(ControlSection);
    

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

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

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

    
    DeleteCriticalSection ( ControlSection );
    

    像往常一样释放你的线程。

    问候 拉斐尔

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        猜你喜欢
        • 2011-11-09
        • 2014-02-13
        • 2011-05-03
        • 1970-01-01
        • 1970-01-01
        • 2011-05-08
        • 2012-03-23
        • 1970-01-01
        相关资源
        最近更新 更多