views:

1076

answers:

3

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?

+1  A: 

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 http://stackoverflow.com/questions/1418333/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

Lars Truijens
+5  A: 

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
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
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.)
moodforaday
@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
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?
moodforaday
@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
+1  A: 

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;
kibab
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.
kibab
Above comment was for @smasher (but he deleted his comment...)
kibab
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.
Smasher
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
Okay, just found at how to take back the downvote...a little edit did the trick :)
Smasher