在Delphi中当线程不需要时安全地暂停和恢复线程的自我挂起

8
这个问题涉及到Delphi和XE特别弃用Suspend和Resume。我已经阅读了其他帖子,目前还没有找到类似的用法,因此我想提出讨论。

我想知道是否有更好的方法来暂停线程,当不需要它时?

我们有一个Delphi类,这个类基本上是与线程处理相关联的FIFO队列。该队列在主线程上接受数据对象,如果线程被挂��,它将恢复它。

作为线程执行过程的一部分,对象从队列中弹出并在线程上进行处理。通常这是为了进行数据库查找。

在处理结束时,对象的属性会被更新并标记为可用于主线程或传递到另一个队列。Execute过程的最后一步(实际上是第一步)是检查队列中是否还有其他项。如果有,则继续,否则它会自我挂起。

关键在于只有在完成Execute循环时才会进行唯一的挂起操作,并且在正常操作期间唯一的恢复操作是在将新项放入队列时调用。例外情况是队列类正在被终止时。

恢复功能看起来像这样。

process TthrdQueue.MyResume();
  begin
    if Suspended then begin
      Sleep(1); //Allow thread to suspend if it is in the process of suspending
      Resume();
    end;
  end;

执行看起来类似于这样。
process TthrdQueue.Execute();
  var
    Obj : TMyObject;
  begin
    inherited;
    FreeOnTerminate := true;
    while not terminated do begin
      if not Queue.Empty then begin
        Obj :=  Pop();
        MyProcess(Obj);  //Do work
        Obj.Ready := true;
      end
      else
        Suspend();  // No more Work
    end;   //Queue clean up in Destructor
  end;  

TthrdQueue的Push例程在将另一个对象添加到堆栈后调用MyResume。仅当线程被挂起时,MyResume才会调用Resume。

关闭时,我们设置terminate为true,并在其被挂起时调用MyResume。


我不是专家,但我会简单地使用Sleep(250)或SignalObjectAndWait(请参阅msdn)以获得更精确的控制。 - Ville Krumlinde
3个回答

8
我建议采用以下实现方式来实现TthrdQueue:

我建议采用以下实现方式来实现TthrdQueue:

type
  TthrdQueue = class(TThread)
  private
    FEvent: THandle;
  protected
    procedure Execute; override;
  public
    procedure MyResume;
  end;

implementation

procedure TthrdQueue.MyResume;
begin
  SetEvent(FEvent);
end;

procedure TthrdQueue.Execute;
begin
  FEvent:= CreateEvent(nil,
                       False,    // auto reset
                       False,    // initial state = not signaled
                       nil);
  FreeOnTerminate := true;
  try
    while not Terminated do begin
      if not Queue.Empty then begin
        Obj :=  Pop();
        MyProcess(Obj);  //Do work
        Obj.Ready := true;
      end
      else
        WaitForSingleObject(FEvent, INFINITE);  // No more Work
    end;
  finally
    CloseHandle(FEvent);
  end;
end;

2
他提到他正在使用Delphi XE,因此他已经可以访问Generics.Collections单元中的TThreadedQueue泛型类。TThreadedQueue是一个FIFO队列,具有在推入和弹出时同步线程的能力。 - vcldeveloper
1
我们的库仍然支持大量的Delphi 7代码。但是我会在未来的努力中考虑TThreadedQueue。我喜欢这个解决方案,因为它可以轻松更新我们现有的代码。 - Rich Shealer
这看起来是一个不错的解决方案,但你什么时候会调用MyResume呢?队列会做到吗?什么时候会调用?它如何知道唤醒哪些线程?或者你只是在队列后面添加一个项目时立即唤醒所有正在睡眠的线程。 - GolezTrol
该示例包含竞态条件:当创建一个TthrdQueue对象并调用MyResume()时,事件句柄可能尚未初始化。我会将CreateEvent()调用放入线程构造函数中,并将相应的CloseHandle()放入析构函数中以避免这种情况。 - blerontin

3

不要挂起线程,而是使其休眠。将其阻塞在某个可等待句柄上,当句柄变为可信号状态时,线程将唤醒。

你有许多可阻塞对象的选项,包括事件、互斥量对象、信号量、消息队列、管道等。

假设你选择使用事件。将其设置为自动复位事件。当队列为空时,调用事件的WaitFor方法。当其他内容填充队列或想要退出时,让它调用事件的SetEvent方法。

我更喜欢使用操作系统消息队列的技术。我会用消息替换你的队列对象。然后编写一个标准的GetMessage循环。当队列为空时,它将自动阻塞以等待新消息。将终止请求转换为另一条消息。(一旦开始使用线程进行任何有趣的操作,TThread.Terminate方法就不是非常有用的函数,因为它不是虚函数。)


我明白你的意思,使用Windows来支持队列。我们一直在使用的是很多现有代码,现在已经足够好了。但是我肯定会在未来的工作中研究它,我喜欢它的自动化特性。谢谢。 - Rich Shealer

3

有一个库可以使用条件变量在Delphi中实现生产者-消费者队列。这种情况实际上是讨论的例子。

条件变量的经典示例是生产者/消费者问题。一个或多个称为生产者的线程生产项目并将其添加到队列中。消费者(其他线程)通过从队列中移除生产的项目来消耗它们。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接