Self Suspending a thread in Delphi when it's not needed and safely resuming
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.
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;
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.)
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.
精彩评论