Self Suspending a thread in Delphi when it's not needed and safely resuming
Asked Answered
P

3

8

This question involves Delphi and XE specifically deprecating Suspend and Resume. I have read other posts and I have not found a similar usage so far, so I’m going to go ahead and ask for a discussion.

What I’d like to know is there a better way to pause a thread when it is not needed?

We have a Delphi class that we have used for years that is basically a FIFO Queue that is associated with a threaded process. The queue accepts a data object on the main thread and if the thread is suspended it will resume it.

As part of the thread’s Execute process the object is popped out of the queue and processed on the thread. Usually this is to do a database lookup.

At the end of the process a property of the object is updated and marked as available to the main thread or passed on to another queue. The last (well it really is the first) step of the Execute process is to check if there are any more items in the queue. If there is it continues, otherwise it suspends itself.

They key is the only suspend action is inside the Execute loop when it is completed, and the only resume during normal operations is called when a new item is placed in the queue. The exception is when the queue class is being terminated.

The resume function looks something like this.

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;

The execute looks similar to this

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;  

The TthrdQueue Push routine calls MyResume after adding another object in the stack. MyResume only calls Resume if the thread is suspended.

When shutting down we set terminate to true and call MyResume if it is suspended.

Pelfrey answered 9/12, 2010 at 17:19 Comment(1)
I'm no expert but I would simply use Sleep(250) or SignalObjectAndWait (see msdn) for more exact control.Mossbunker
A
8

I'd recommend the following implementation of 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;
Atween answered 9/12, 2010 at 18:2 Comment(4)
He mentioned he is on Delphi XE, so he already has access to TThreadedQueue generic class in Generics.Collections unit. TThreadedQueue is a FIFO queue with the ability of synchronizing threads while pushing and popping.Rees
We still support a lot of Delphi 7 code in our libraries. But I will look at TThreadedQueue for future endevours. I like the given solution for its ease in updating our existing code.Pelfrey
This looks like a good solution, but when are you going to call MyResume? Will the queue do that? And when? How does it know which threads to wake up? Or would you just wake up all sleeping threads as soon as an item is added at the back of the queue.Harve
The example contains a race condition: When creating a TthrdQueue object and calling MyResume() the event handle might not have been initialized yet. I'd put the CreateEvent() call into the thread constructor, and the corresponding CloseHandle() into the destructor to avoid that.Wardlaw
A
3

Instead of suspending the thread, make it sleep. Make it block on some waitable handle, and when the handle becomes signalled, the thread will wake up.

You have many options for waitable objects, including events, mutex objects, semaphores, message queues, pipes.

Suppose you choose to use an event. Make it an auto-reset event. When the queue is empty, call the event's WaitFor method. When something else populates the queue or wants to quit, have it call the event's SetEvent method.

I preferred technique is to use the OS message queue. I'd replace your queue object with messages. Then, write a standard GetMessage loop. When the queue is empty, it will automatically block to wait for a new message. Turn a termination request into just another message. (The TThread.Terminate method simply isn't a very useful function once you start doing anything interesting with threads because it's not virtual.)

Alurd answered 9/12, 2010 at 18:4 Comment(1)
I see what you mean using Windows to support the queues. What we have been using is in a lot of exisiting code and for now is good enough. But I'll definetly look into it for future work, I do like the automatic nature of it. Thank you.Pelfrey
C
3

There is a library to allow implementation of producer-consumer queue in Delphi using condition variables. This scenario is actually the example discussed.

The classic example of condition variables is the producer/consumer problem. One or more threads called producers produce items and add them to a queue. Consumers (other threads) consume items by removing the produced items from the queue.

Chorizo answered 9/12, 2010 at 18:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.