自动或手动释放 TThread

发布于 2024-09-15 23:01:02 字数 654 浏览 6 评论 0原文

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

我了解 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;

I have a main thread and a separate thread in my program. If the separate thread finishes before the main thread, it should free itself automatically. If the main thread finishes first, it should free the separate thread.

I know about FreeOnTerminate, and I've read that you have to be careful using it.

My question is, is the following code correct?

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;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

屋檐 2024-09-22 23:01:02

您可以将其简化为:

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

You can simplify this to:

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;

Explanation:

  • Either use FreeOnTerminate or free the thread manually, but never do both. The asynchronous nature of the thread execution means that you run a risk of not freeing the thread or (much worse) doing it twice. There is no risk in keeping the thread object around after it has finished the execution, and there is no risk in calling Terminate() on a thread that has already finished either.

  • There is no need to synchronize access to a boolean that is only written from one thread and read from another. In the worst case you get the wrong value, but due to the asynchronous execution that is a spurious effect anyway. Synchronization is only necessary for data that can not be read or written to atomically. And if you need to synchronize, don't use Synchronize() for it.

  • There is no need to have a variable similar to MyThreadReady, as you can use WaitForSingleObject() to interrogate the state of a thread. Pass MyThread.Handle as the first and 0 as the second parameter to it, and check whether the result is WAIT_OBJECT_0 - if so your thread has finished execution.

BTW: Don't use the OnClose event, use OnDestroy instead. The former isn't necessarily called, in which case your thread would maybe continue to run and keep your process alive.

暗恋未遂 2024-09-22 23:01:02

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

Have the main thread assign a handler to the worker thread's OnTerminate event. If the worker thread finishes first, then the handler can signal the main thread to free the thread. If the main thread finishes first, it can terminate the worker thread. For example:

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;
七七 2024-09-22 23:01:02

不,你的代码不好(尽管它可能在 99.99% 甚至 100% 的情况下都能工作)。如果您打算从主线程终止工作线程,请不要将 FreeOnTerminate 设置为 True (我不明白通过将 FreeOnTerminate 设置为 True 在上面的代码中您想要获得什么,它至少使您的代码更难以理解) 。

终止工作线程的一个更重要的情况是,您试图在工作线程处于等待状态时关闭应用程序。如果你只是调用Terminate,线程不会被唤醒,通常你应该使用额外的同步对象(通常是事件)来唤醒工作线程。

不需要

  begin
    MyThread.Terminate;
    MyThread.WaitFor;
    MyThread.Free;
  end;

还有一点需要注意的是,如果您查看 TThread.Destroy 代码,则

    MyThread.Free;

,它会调用 Terminate 和 WaitFor,因此就足够了(至少在 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 对象。为了安全起见,成本并不高。

No, your code is not good (though it probably will work in 99.99% or even 100% cases). If you are planning to terminate work thread from main thread, don't set FreeOnTerminate to True (I don't see what are you trying to gain in the above code by setting FreeOnTerminate to True, it at least makes your code less understandable).

A more important situation with terminating work threads is that you are trying to close an application while work thread is in wait state. The thread will not be awaken if you just call Terminate, generally you should use additional syncronization object (usually event) to wake up the work thread.

And one more remark - there is no need for

  begin
    MyThread.Terminate;
    MyThread.WaitFor;
    MyThread.Free;
  end;

if you look at TThread.Destroy code, it calls Terminate and WaitFor, so

    MyThread.Free;

is enough (at least in Delphi 2009, have no Delphi 7 sources at hand to check).


Updated

Read mghie answer. Consider the following situation (better on 1 CPU system):

main thread is executing

procedure TMainForm.Close;
begin
  if not MyThreadReady then
  begin
    MyThread.Terminate;
    MyThread.WaitFor;
    MyThread.Free;
  end;
end;

it checked MyThreadReady value (it is False) and was switched off by scheduler.

Now scheduler switches to work thread; it executes

  Synchronize(ThreadFinished);

and forces scheduler to switch back to main thread. Main thread continues execution:

    MyThread.Terminate;   // no problem
    MyThread.WaitFor;     // ???
    MyThread.Free;

can you say what will happen at WaitFor? I can't (requires a deeper look into TThread sources to answer, but at first glance looks like a deadlock).

Your real error is something different - you have written an unreliable code and trying to find out is it correct or not. That is bad practice with threads - you should learn to write a reliable code instead.

As for resources - when the TThread (with FreeOnTerminate = False) is terminated the only resources that remains allocated is Windows thread handle (it does not use substantial Windows resources after thread is terminated) and Delphi TThread object in memory. Not a big cost to be on the safe side.

你的呼吸 2024-09-22 23:01:02

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

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

I would state that mixing models is simply not recommended. You either use FreeOnTerminate and never touch the thread again, or you don't. Otherwise, you need a protected way for the two to communicate.

Since you want fine control over the thread variable, then don't use FreeOnTerminate. If your thread finishes early, clear the local resources that the thread has consumed as you normally would, and then simply let the main thread free the child thread when the application is finished. You'll get the best of both worlds - resources freed by the child thread as soon as it can be, and no worries about thread synchronization. (And it's got the added bonus of being much simpler in design/code/understanding/support...)

嘿哥们儿 2024-09-22 23:01:02

老实说,你的


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

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

在表单 public 部分,声明:

ControlSection : TRTLCriticalSection;

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

InitializeCriticalSection(ControlSection);

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


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

在执行之前(退出),调用


DeleteCriticalSection ( ControlSection );

并释放线程:你总是这样。

问候
拉斐尔

Honestly, your


... Do some processing

Is the real problem here. Is that a loop for doing something recursively? If not and, instead, thats a huge task, you should consider split this task in small procedures / functions, and put all together in the execute body, calling one after another with conditional if's to know the thread state, like:

 

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;

It is dirty, almost a hack, but will work as expected.

About FreeOnTerminate, well... just remove the declaration and always


FreeAndNil(ThreadObject);

I'm not a fan of syncronise. I like more critical sections, for the flexibility to extend the code to handle more shared data.

On the form public section, declare:

ControlSection : TRTLCriticalSection;

On form create or somewhere else before thread.create ,

InitializeCriticalSection(ControlSection);

Then, every time you write to a shared resource (including your MyThreadReady variable), do


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

Before you go (exit), call


DeleteCriticalSection ( ControlSection );

and free your thread as you always do.

Regards
Rafael

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文