1

I've been looking for a way to monitor for specific registry changes in Delphi. Found a solution at about.com:

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;

In my application I will need to start and stop this thread on demand, but the above code does not permit that. Just setting the Terminated flag won't do.

It would be sufficient to somehow tell the thread to stop waiting, then free it and create a new one when needed. How can I change this code to achieve that?

Marek Jedliński
  • 7,088
  • 11
  • 47
  • 57

3 Answers3

8

Use WaitForMultipleObjects() with an array of two events instead of WaitForSingleObject(). Add a manual reset event to the thread class, and signal it after you have set Terminated to True. Check the return value which of the two events has been signalled, and act accordingly.

Edit:

Some minimal Delphi 2009 code to demonstrate the idea. You have to add SyncObjs to the list of used units, and add

  fTerminateEvent: TEvent;

to the private section of your thread class.

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;

You only need to add the code in your question to it. Simply trying to free the thread instance will do everything necessary to unblock the thread (if necessary).

mghie
  • 32,028
  • 6
  • 87
  • 129
  • That's what I do, except I don't even bother using `Terminated`. That property isn't a thread-friendly mechanism. I use a termination event exclusively, and then simply provide a new method for other threads to call if they want to stop the thread. – Rob Kennedy Nov 14 '09 at 16:27
  • This would be my preferred method, but how would I create that custom event, and how would it be sent to a thread that has no window handle to receive messages? (I don't quite understand the various (Msg)WaitForXXX APIs and how they should be used in Delphi. The APIs ostensibly wait for "objects", but there are no objects in the Delphi sense, and "events" don't seem to mean what they mean in Delphi, either. It's an awfully confusing area to me.) – Marek Jedliński Nov 14 '09 at 20:35
  • @Rob: Setting `Terminated` is indeed not necessary, but I do it anyway in case there is thread code that checks for it. Otherwise thread shutdown would take longer than strictly necessary. – mghie Nov 14 '09 at 21:17
  • Thanks, mghie! This seems to cover everything. One last thing I'm not quite clear on is how to know which of the two events caused the wait to end. Your check for WaitForMultipleObjects() <> WAIT_OBJECT_0 implies that the "custom" fTerminateEvent does not return WAIT_OBJECT_0, is this correct? More generally, if I had more than one custom event (to send more than just a "quit" command to the thread), how could I know which of my custom events got signaled? – Marek Jedliński Nov 15 '09 at 17:12
  • @moodforaday: Check the MSDN page for `WaitForMultipleObjects()`. For *N* handles that you wait on (without `bWaitAll`!) it will return one of `WAIT_OBJECT_0` to `WAIT_OBJECT_0 + N - 1` if any of the handles became signalled, and some other value otherwise. I chose to break out of the loop (and terminate the thread) for all other return values but the registry change notification handle becoming signalled. That handle is the first one in the handle array, so the return value for it is `WAIT_OBJECT_0`. – mghie Nov 15 '09 at 17:22
1

Instead in INFINITE you should have WaitForSingleObject time out after a period. That way the loop continues and Terminated is checked.

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;

The methods TThread.Suspend and TThread.Resume could theoretically be used to temporary stop threads, but as Delphi 2010 now acknowledges they are not safe for use. See TThread.resume is deprecated in Delphi-2010 what should be used in place? and http://msdn.microsoft.com/en-us/library/ms686345%28VS.85%29.aspx

Community
  • 1
  • 1
Lars Truijens
  • 42,837
  • 6
  • 126
  • 143
1

This works, just make small changes as below and now when you call 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;
jpfollenius
  • 16,456
  • 10
  • 90
  • 156
Krystian Bigaj
  • 1,295
  • 8
  • 14
  • You're wrong, it works. The most important line is Windows.SetEvent(FEvent) which makes event object signaled, so function WaitForSingleObject returns WAIT_OBJECT_0. Best would be also call that new Terminated procedure at the begining of Destroy procedure, so you could stop monitoring immediatelly when destroying thread (but in this example there is FreeOnTerminate so it should not be destryed by hand). To stop monitoring call Terminate (new one), and to start create new thread. Also this code from about.com leaks handle, there is lack of CloseHandle(FEvent) in Destroy procedure. – Krystian Bigaj Nov 14 '09 at 19:43
  • Above comment was for @smasher (but he deleted his comment...) – Krystian Bigaj Nov 14 '09 at 19:52
  • Sorry about that. I missed your `SetEvent` call and when I saw it deleted my comment. Tried to take my downvote back too, but unfortunately I couldn't. – jpfollenius Nov 14 '09 at 22:33
  • While this would work in this case it isn't a general solution, as there are objects that can be waited on with `WaitForSingleObject()` that can not be signalled manually. – mghie Nov 15 '09 at 17:47
  • Okay, just found at how to take back the downvote...a little edit did the trick :) – jpfollenius Nov 17 '09 at 21:57