【问题标题】:How to start/stop a monitoring Delphi thread on demand?如何按需启动/停止监控 Delphi 线程?
【发布时间】:2010-12-16 14:51:32
【问题描述】:

我一直在寻找一种方法来监视 Delphi 中的特定注册表更改。在 about.com 找到solution

procedure TRegMonitorThread.Execute;
begin
  InitThread; // method omitted here
  while not Terminated do
  begin
    if WaitForSingleObject(FEvent, INFINITE) = WAIT_OBJECT_0 then
    begin
      fChangeData.RootKey := RootKey;
      fChangeData.Key := Key;
      SendMessage(Wnd, WM_REGCHANGE, RootKey, LongInt(PChar(Key)));
      ResetEvent(FEvent);

      RegNotifyChangeKeyValue(FReg.CurrentKey, 1, Filter, FEvent, 1);
    end;
  end;
end;

在我的应用程序中,我需要按需启动和停止该线程,但上面的代码不允许这样做。仅仅设置 Terminated 标志是不行的。

以某种方式告诉线程停止等待,然后释放它并在需要时创建一个新线程就足够了。如何更改此代码以实现此目的?

【问题讨论】:

    标签: multithreading delphi ondemand


    【解决方案1】:

    WaitForMultipleObjects() 与包含两个事件的数组一起使用,而不是WaitForSingleObject()。将手动重置事件添加到线程类,并在将Terminated 设置为True 后发出信号。检查两个事件中的哪个事件的返回值,并采取相应的行动。

    编辑:

    一些最小的 Delphi 2009 代码来演示这个想法。您必须将SyncObjs 添加到使用的单位列表中,然后添加

      fTerminateEvent: TEvent;
    

    到你的线程类的private 部分。

    constructor TTestThread.Create;
    begin
      inherited Create(TRUE);
      fTerminateEvent := TEvent.Create(nil, True, False, '');
      // ...
      Resume;
    end;
    
    destructor TTestThread.Destroy;
    begin
      fTerminateEvent.SetEvent;
      Terminate; // not necessary if you don't check Terminated in your code
      WaitFor;
      fTerminateEvent.Free;
      inherited;
    end;
    
    procedure TTestThread.Execute;
    var
      Handles: array[0..1] of THandle;
    begin
      Handles[0] := ...; // your event handle goes here
      Handles[1] := fTerminateEvent.Handle;
      while not Terminated do begin
        if WaitForMultipleObjects(2, @Handles[0], False, INFINITE) <> WAIT_OBJECT_0 then
          break;
        // ...
      end;
    end;
    

    您只需将问题中的代码添加到其中。简单地尝试释放线程实例将完成解除线程阻塞所需的一切(如果需要)。

    【讨论】:

    • 这就是我所做的,除了我什至懒得使用Terminated。该属性不是线程友好的机制。我专门使用终止事件,然后简单地提供一个新方法供其他线程调用,如果他们想停止线程。
    • 这将是我的首选方法,但我将如何创建该自定义事件,以及如何将其发送到没有窗口句柄来接收消息的线程? (我不太了解各种 (Msg)WaitForXXX API 以及它们应该如何在 Delphi 中使用。API 表面上等待“对象”,但在 Delphi 意义上没有对象,“事件”似乎没有也就是他们在 Delphi 中的意思。这对我来说是一个非常令人困惑的领域。)
    • @Rob:确实没有必要设置Terminated,但无论如何我都会这样做,以防有线程代码检查它。否则线程关闭将花费比严格必要的时间更长的时间。
    • 谢谢,mghie!这似乎涵盖了一切。我不太清楚的最后一件事是如何知道这两个事件中的哪一个导致等待结束。您对 WaitForMultipleObjects() WAIT_OBJECT_0 的检查意味着“自定义”fTerminateEvent 不返回 WAIT_OBJECT_0,这是正确的吗?更一般地说,如果我有多个自定义事件(不仅仅是向线程发送“退出”命令),我怎么知道我的哪些自定义事件收到了信号?
    • @moodforaday:查看 MSDN 页面中的 WaitForMultipleObjects()。对于您等待的 N 句柄(没有bWaitAll!),如果任何句柄发出信号,它将返回WAIT_OBJECT_0WAIT_OBJECT_0 + N - 1 之一,否则返回一些其他值。对于所有其他返回值,我选择跳出循环(并终止线程),但注册表更改通知句柄会发出信号。该句柄是句柄数组中的第一个句柄,因此它的返回值为WAIT_OBJECT_0
    【解决方案2】:

    在 INFINITE 中,您应该在一段时间后让 WaitForSingleObject 超时。这样循环会继续,并检查 Terminated。

    procedure TRegMonitorThread.Execute;
    begin
      InitThread; // method omitted here
      while not Terminated do
      begin
        if WaitForSingleObject(FEvent, 1000) = WAIT_OBJECT_0 then
        begin
          fChangeData.RootKey := RootKey;
          fChangeData.Key := Key;
          SendMessage(Wnd, WM_REGCHANGE, RootKey, LongInt(PChar(Key)));
          ResetEvent(FEvent);
    
          RegNotifyChangeKeyValue(FReg.CurrentKey, 1, Filter, FEvent, 1);
        end;
      end;
    end;
    

    TThread.Suspend 和 TThread.Resume 方法理论上可以用来临时停止线程,但 Delphi 2010 现在承认它们不安全使用。见TThread.resume is deprecated in Delphi-2010 what should be used in place?http://msdn.microsoft.com/en-us/library/ms686345%28VS.85%29.aspx

    【讨论】:

      【解决方案3】:

      这行得通,只需进行如下的小改动,现在当您调用 Terminate 时:

        TRegMonitorThread = class(TThread)
        ...
        public
          procedure Terminate; reintroduce;
      ...
      
      procedure TRegMonitorThread. Terminate;  // add new public procedure
      begin
        inherited Terminate;
        Windows.SetEvent(FEvent);
      end;
      
      procedure TRegMonitorThread.Execute;
      begin
        InitThread;
      
        while not Terminated do
        begin
          if WaitForSingleObject(FEvent, INFINITE) = WAIT_OBJECT_0 then
          begin
            if Terminated then // <- add this 2 lines
              Exit;
            ...
          end;
        end;
      end;
      

      【讨论】:

      • 你错了,它有效。最重要的一行是 Windows.SetEvent(FEvent),它使事件对象发出信号,因此函数 WaitForSingleObject 返回 WAIT_OBJECT_0。最好也可以在 Destroy 过程开始时调用新的 Terminated 过程,因此您可以在销毁线程时立即停止监视(但在此示例中存在 FreeOnTerminate,因此不应手动销毁它)。停止监视调用终止(新的),并开始创建新线程。另外,来自 about.com 的这段代码泄漏了句柄,在 Destroy 过程中缺少 CloseHandle(FEvent)。
      • 以上评论是针对@smasher(但他删除了他的评论...)
      • 很抱歉。我错过了你的SetEvent 电话,当我看到它时删除了我的评论。也试图收回我的反对票,但不幸的是我做不到。
      • 虽然这在这种情况下可行,但它不是一个通用的解决方案,因为有些对象可以通过 WaitForSingleObject() 等待,但无法手动发出信号。
      • 好的,刚刚发现如何收回反对票...稍微修改一下就可以了 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-09-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多