【问题标题】:Delphi thread that waits for data, processes it, then resumes waitingDelphi 线程等待数据,处理它,然后继续等待
【发布时间】:2010-12-16 16:09:54
【问题描述】:

我需要在 Delphi 中创建一个具有以下特征的线程:

  • 等到主线程将数据添加到共享队列。
  • 处理队列中的所有数据,将结果返回到主线程(在最后一部分,我将只向主窗口发送消息)。处理非常耗时,因此在工作线程处理之前的条目时,可能会将新数据添加到队列中。
  • 继续等待,使用尽可能少的 CPU 周期。

我无法向线程发送消息,因为它没有窗口句柄。

我应该使用 WaitForObject 的一些变体吗?如果是这样,那还要等什么呢?如果没有,那我怎样才能让线程等待,然后在新数据进入队列时唤醒它?

我读过Multithreading - The Delphi Way,它似乎没有回答我的问题。也许OmniThreadLibrary可以做我需要的;我不知道,因为文档很少。我对线程的了解还不够,无法弄清楚该库是否会在这里提供帮助以及如何使用它(甚至为什么要使用它而不是仅仅使用 TThread 后代)。

【问题讨论】:

  • 您写道:“我读过多线程 - 德尔福之道,这似乎无法回答我的问题。”但在第 9 章中确实如此。生产者 - 消费者 - 关系正是您要寻找的,而信号量确实是实现此类队列的一种方式。

标签: multithreading delphi queue resume suspend


【解决方案1】:

OmniThreadLibrary 在这里绝对可以为您提供帮助。 OTL 发行版中的测试 5 应该可以帮助您入门。

在此演示中,“开始”按钮创建线程并设置一些参数和计时器(如果不需要,您可以在代码中删除它们)。 “更改消息”向线程发送消息,该消息在线程的 OMChangeMes​​sage 方法中处理。然后线程将一些信息发送回客户端(本演示中为 OMSendMessage,但您可以在您将在其中进行工作的同一消息中执行此操作)并且主线程通过 OmniEventMonitor 组件接收此消息。 “停止”按钮停止工作线程。

如果在您的线程忙碌时有更多消息到达,它们将在您的工作方法完成其工作后立即排队和处理。当无事可做时,线程将在进程中使用零 CPU 周期等待下一条消息。

编辑

在 Delphi 2009 及更高版本中,Background Worker 模式提供了更简单的解决方案。

【讨论】:

  • 是的,我知道,我知道...... :( 文档是 1.04 版本之后列表中的第一件事。
【解决方案2】:

您绝对可以向线程发送消息,即使它没有窗口句柄。只需使用PostThreadMessage() 而不是SendMessage()PostMessage()。如果您在 [delphi] 标记中搜索 PostThreadMessage(),这里将会有更多关于 StackOverflow 的信息 - 我认为在这里复制所有内容不是一个好主意。

但如果你不了解线程编程,那么从 OTL 开始而不是低级的东西可能确实是一件好事。

【讨论】:

    【解决方案3】:

    WaitForSingleObject() 可以等待多种类型的同步对象。您可以使用 Windows“事件”同步对象(与 Delphi 事件无关)。您创建事件(在 SyncObjs、IIRC 中有一个 Delphi TEvent 包装器),然后调用 WaitForSingleObject 以等待该事件发出信号。当您必须唤醒线程时,您调用 SetEvent 将事件置于信号状态,然后 WaitForSingleObject 返回。您可以使用 WaitForMultipleObjects() 让线程等待多个对象中的一个(或全部) - 它还会告诉您哪个对象已发出信号。

    【讨论】:

      【解决方案4】:

      这是一个简单的例子,你可以如何做到这一点......

      const
        WM_MY_RESULT = WM_USER + $1;
      
      type
        TMyThread = class(TThread)
        private
          FKilled: Boolean;
          FListLock: TRTLCriticalSection;
          FList: TList;
          FJobAdded: TEvent;
        protected
          procedure Execute; override;
          procedure DoJob(AJob: Integer);
        public
          constructor Create(CreateSuspended: Boolean);
          destructor Destroy; override;
          procedure Kill;
          procedure PushJob(AJob: Integer);
          function  JobCount: Integer;
          function  GetJob: Integer;
        end;
      
      
        TThreadingForm = class(TForm)
          lstResults: TListBox;
          se: TSpinEdit;
          btn: TButton;
          procedure FormCreate(Sender: TObject);
          procedure FormDestroy(Sender: TObject);
          procedure btnClick(Sender: TObject);
        private
          FThread: TMyThread;
          procedure OnMyResultMessage(var Msg: TMessage); message WM_MY_RESULT;
        public
          { Public declarations }
        end;
      
      var
        ThreadingForm: TThreadingForm;
      
      implementation
      
      {$R *.dfm}
      
      { TMyThread }
      
      constructor TMyThread.Create(CreateSuspended: Boolean);
      begin
        FKilled := False;
        InitializeCriticalSection(FListLock);
        FList := TList.Create;
        FJobAdded := TEvent.Create(nil, True, False, 'job.added');
        inherited;
      end;
      
      destructor TMyThread.Destroy;
      begin
        FList.Free;
        FJobAdded.Free;
        DeleteCriticalSection(FListLock);
        inherited;
      end;
      
      procedure TMyThread.DoJob(AJob: Integer);
      var
        res: Integer;
      begin
        res := AJob * AJob * AJob * AJob * AJob * AJob;
        Sleep(1000); // so it would take some time
        PostMessage(ThreadingForm.Handle, WM_MY_RESULT, res, 0);
      end;
      
      procedure TMyThread.Execute;
      begin
        inherited;
        while not FKilled or not Self.Terminated do
        begin
          EnterCriticalSection(FListLock);
          if JobCount > 0 then
          begin
            LeaveCriticalSection(FListLock);
            DoJob(GetJob)
          end
          else
          begin
            FJobAdded.ResetEvent;
            LeaveCriticalSection(FListLock);
            FJobAdded.WaitFor(10000);
          end;
        end;
      end;
      
      function TMyThread.GetJob: Integer;
      begin
        EnterCriticalSection(FListLock);
        try
          Result := Integer(FList[0]);
          FList.Delete(0);
        finally
          LeaveCriticalSection(FListLock);
        end;
      end;
      
      function TMyThread.JobCount: Integer;
      begin
        EnterCriticalSection(FListLock);
        Result := FList.Count;
        LeaveCriticalSection(FListLock);
      end;
      
      procedure TMyThread.Kill;
      begin
        FKilled := True;
        FJobAdded.SetEvent;
        Terminate;
      end;
      
      procedure TMyThread.PushJob(AJob: Integer);
      begin
        EnterCriticalSection(FListLock);
        try
          FList.Add(Pointer(AJob));
          FJobAdded.SetEvent;
        finally
          LeaveCriticalSection(FListLock);
        end;
      end;
      
      { TThreadingForm }
      
      procedure TThreadingForm.OnMyResultMessage(var Msg: TMessage);
      begin
        lstResults.Items.Add(IntToStr(Msg.WParam));
      end;
      
      procedure TThreadingForm.FormCreate(Sender: TObject);
      begin
        FThread := TMyThread.Create(False);
      end;
      
      procedure TThreadingForm.FormDestroy(Sender: TObject);
      begin
        FThread.Kill;
        FThread.WaitFor;
        FThread.Free;
      end;
      
      procedure TThreadingForm.btnClick(Sender: TObject);
      begin
        FThread.PushJob(se.Value);
      end;
      

      【讨论】:

      • 您的线程类编码错误。当它访问 Execute 方法中的一个私有对象字段时,当它们已经在析构函数中被释放时,它将引发一个 AV。为了防止你需要在释放任何东西之前终止线程并WaitFor它。
      • 恕我直言,对进入/离开临界区的调用应由 try..finally 包装,以避免在发生异常时使 CS 锁定。
      • 临界区的这些东西大部分都不会抛出异常。
      • 好吧,在这种情况下,“大多数”是不够的。这必须只发生一次,并且您处于死锁状态,如果不重新启动程序就无法逃脱。使用try..finally 只是一个好习惯。 99% 不需要,剩下的 1% 会为您省去很多问题。
      • 即使是 CW,我也不确定这是否可以轻松更改为真正正确的。例如,使用手动重置事件也存在问题,因为可能在检查 JobCount > 0ResetEvent 之间将另一个作业添加到队列中,但事件仍会(错误地)重置.信号量不会有这个问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-25
      • 1970-01-01
      • 1970-01-01
      • 2018-05-19
      • 1970-01-01
      相关资源
      最近更新 更多