Safely building a custom thread as base for descendants - multithreading

I'm writing a custom thread which includes some added functionality. The part I'm confused about is how to handle the Execute procedure, while still expecting it to be descended into more inherited implementations.
My custom thread is overriding the Execute procedure and adding some of my own stuff, such as events OnStart, OnStop and OnException, as well as looping capabilities. I'm not sure how to design this in a way that expects it to be further used in a further inherited thread.
How do I make it possible to further inherit this custom thread while maintaining the Execute functionality?
Here's the execute procedure as I have overridden it...
procedure TJDThread.Execute;
begin
Startup;
try
while not Terminated do begin
if assigned(FOnExecute) then
FOnExecute(Self);
if not FRepeatExec then
Terminate
else
if FExecDelay > 0 then
Sleep(FExecDelay);
end;
finally
Cleanup;
end;
end;
I'm intending for FOnExecute to be actually an event of the thread, which is more-so a replacement of inheriting the Execute procedure - similar to how a service works. I don't think this is the right way to go... How do I make sure this is coded in a safe manner? I'm open to suggestions to another approach than an event - so long as it's aimed at the goal of making a custom TThread which can be inherited and further executed.
This custom thread I'm making includes some additional capabilities which don't come with the original TThread and yet will be extremely useful for many future projects. The additional capabilities are specifically OnStart and OnStop events (similar to how a service works), CoInitialize built in (and only used if told to, default = false), Repeated execution (default = false), and delay between executions (default = 0).

I agree with Rob. Don't use an event, use a virtual method. But even if you were to use the event and employ its "assignedness" to signal whether there is work to be done, you would need to protect the FOnExecute member as it can be set from different threads.
In one of our thread classes we use commands to do something similar:
procedure TCommandThread.SetCommand(const Value: ICommand);
begin
Lock;
try
Assert(not IsAvailable, 'Command should only be set AFTER the thread has been claimed for processing');
FCommand := Value;
if Assigned(FCommand) then
MyEvent.SetEvent;
finally
Unlock;
end;
end;
As SetCommand (the Command's setter) can be called from any ol' thread, setting the FCommand member is protected by the thread's critical section which is locked and unlocked through the Lock and Unlock methods.
Signalling MyEvent is done because our thread class uses a TEvent member to wait for work.
procedure TCommandThread.Execute;
begin
LogDebug1.SendFmtMsg('%s.Execute : Started', [ClassName]);
// keep running until we're terminated
while not Terminated do
try
// wait until we're terminated or cleared for take-off by the threadpool
if WaitForNewCommand then
if Assigned(FCommand)
and not Terminated then
// process the command if we're told to do so
CommandExecute;
except
LogGeneral.SendFmtError('%s.Execute : Exception occurred :', [ClassName]);
LogGeneral.SendException;
end;
LogDebug1.SendFmtMsg('%s.Execute : Finished', [ClassName]);
end;
WaitForNewCommand returns when the MyEvent is signalled. This is done when a command is assigned, but also when a (running) command is cancelled, when the thread is terminated etc. Note that Terminated is checked again just before CommandExecute is called. This is done because when WaitForNewCommand returns, we could be in a situation where both a command was assigned and terminate has been called. After all, signalling the event can be done twice from different threads and we don't know when or in what order anything happened.
CommandExecute is a virtual method that different thread classes can override. In the default implementation it provides for all the status processing around command execution so the commands themselves can concentrate on their own stuff.
procedure TCommandThread.CommandExecute;
var
ExceptionMessage: string;
begin
Assert(Assigned(FCommand), 'A nil command was passed to a command handler thread.');
Assert(Status = chsIdle, 'Attempted to execute non-idle command handler thread');
// check if the thread is ready for processing
if IsAvailable then // if the thread is available, there is nothing to do...
Exit;
try
FStatus := chsInitializing;
InitializeCommand;
FStatus := chsProcessing;
try
ExceptionMessage := '';
CallCommandExecute;
except
on E: Exception do begin
ExceptionMessage := E.Message;
LogGeneral.SendFmtError('%s.CommandExecute: Exception occurred during commandhandler thread execution:', [ClassName]);
LogGeneral.SendException;
end;
end;
finally
FStatus := chsFinalizing;
FinalizeCommand;
FStatus := chsIdle;
FCommand := nil;
// Notify threadpool we're done, so it can terminate this thread if necessary :
DoThreadFinished;
// Counterpart to ClaimThreadForProcessing which is checked in IsAvailable.
ReleaseThreadForProcessing;
end;
end;
CallCommandExecute is where, through several levels of indirection the FCommand's Execute method is called and where the real work of the command is done. That is why that call is directly protected with a try-except block. Other than that each Command in and of itself is responsible for thread safety with regard to the resources it uses.
ClaimThreadForProcessing and ReleaseThreadForProcessing are used to claim and release a thread. For speed's sake they don't use the thread's lock, but use the interlocked mechanism to change the value of the class' FIsAvailable member which is declared as a pointer and used as a boolean:
TCommandThread = class(TThread)
// ...
FIsAvailable: Pointer;
function TCommandThread.ClaimThreadForProcessing: Boolean;
begin
Result := Boolean(CompatibleInterlockedCompareExchange(FIsAvailable, Pointer(False), Pointer(True)));
// (See InterlockedExchange help.)
end;
function TCommandThread.ReleaseThreadForProcessing: Boolean;
begin
FIsAvailable := Pointer(True);
Result := IsAvailable;
end;
If any of the "finally" processing in the CommandExecute method needs to be done regardless of exceptions raised by other calls in that process, you will have to use nested try-finally's to ensure that is the case. The above method was simplified from our real code and the actual finally block is a set of nested try finally's to ensure that DoThreadFinished etc. get called regardless of exceptions in FinalizeCommand (and other calls in between).

Don't worry about how to make it safe to override Execute. Consumers who override your thread's Execute method won't work correctly (because they'll put their own operations around your bookkeeping code instead of within it). Provide a new virtual method for descendants to call instead. You could call it Run, for example, using Indy's TIdThread as a guide. It does much of the same things you're planning on.

Don't call Sleep(FExecDelay) - it's a kernel call that the descendant may not wish to make, so:
if (FExecDelay<>0) then Sleep(FExecDelay);
This gives a user the choice of avoiding the kernel call entirely.
I have issues with TThread.Synchronize - I would not want to force any user to have to call it.
TBH, I'm more used to putting code into an object class that is not descended from TThread, ie. a 'Ttask' that has a 'work' method that is called from the TThread. Having a separate class for the work is hugely more flexible and safer than adding data members and methods to a TThread descendant - it's easily queued in, queued out, PostMessaged etc. That, and not having access to the TThread instance stops developers using TThread.Synchronize, TThread.WaitFor and TThread.OnTerminate, so increasing the reliability and performance of the app.

Related

TThread.CreateAnonymousthread / FreeOnTerminate but then with TTask / ITask

Background
Using the TThread.CreateANonymousThread(aProc:TProc) I can create a thread that destroys the thread object after the thread has terminated. (or alternatively by setting FreeOnTerminate to true for athread object). This allws the thread initiator routine th finish and go out of scope, while the tread keeps on running. (That's what I am looking for)
procedure StartProcess
begin
var lTask:=TThread.CreateAnonymousThread(
procedure
begin
... Do lengthy thread stuff here
end
);
...
lTask.Start;
end;
The problem arises that TTask.Create returns an ITask interface that gets to be released when the thread initiator code drops its context (RefCount drops to 0 -> Destroy gets called), resulting in the thread to generate an AV.
procedure StartProcess
begin
var lTask:=TTask.Create(
procedure
begin
... Do lengthy thread stuff here
end
);
...
lTask.Start;
end; /// past this point, the subthread wil crash because the underlying task object is destroyed
In case of OmniThread we have a solution called IOmniTaskCOntrol.Unobserved that avoids the task object getting destroyed before it is finished.
Why?
EDIT: I like the ITask interface over the TThread class because it allows loose coupling and code injection. (prev: Because TThread might be deprecated: just forget about that)
Question
I was wondering if (and how!) using TTask.Create(aProc:TProc) and the ITask interface the same can be accomplished. Analyzing the source code did not help me so far.
The answer is simple: You don't have to do anything special. The ITask interface returned by the TTask.Create call is also "held onto" internally by the InternalExecute method, so the underying TTask object will be destroyed by means of reference counting. If the "Master" thread does not hold on to the ITask interface, the subthread will. Until it has terminated.
So using TTask this way is pretty straightforward.
NOTE: In RS10.4.2 this works, I suspect using captured interface variables may cause a problem in 10.4.1 and earlier due to inline var problems combined with anonymous procs. (Didn't try)

How Do I Call a Thread.Execute in GUI Thread?

There is a TThread descendant class with its own Execute method doing some math. It works fine but I am looking for the optimization of the following kind. The GUI thread and context of the program determine the count of necessary instances of these threads to be created, run and freed. In certain (rare or user determined) circumstances creation of one instance is enough.
By the moment I use the following construction (in pseudocode):
if ThreadsCount>1 then
begin
Creation of threads
starting them
waiting for results
evaluating and assigning the best result
freeing the threads
end
else
starting the math procedure (edited here separately)
// and in MyThread class declaration
procedure Execute... (edited here separately)
So there are two places in code that have my math procedure and I have to edit both of them if some math changes are applied. The GUI math procedure is a bit different from that one called in thread so I can not simply extract the method and call it.
I wonder if there is a way to create a thread instance and call its Execute method in GUI thread?
You could write some seriously hacky, indescribably bad code to enable you to safely call a TThread's Execute(). But it's an absurd thing to do. The whole point of the TThread class is that it:
starts a new thread in the OS;
then calls Execute() on that thread.
So:
If you don't need a thread, there's absolutely no point in starting a thread that you don't want to use.
You would need to prevent Execute() from doing any processing on its thread-run.
You could then call Execute from the main thread.
But since you have no guarantees how long the thread will take to not do any processing when it calls Execute(), you'd still need to wait for the thread to finish before you can destroy the TThread object.
The GUI math procedure is a bit different from that one called in thread so I can not simply extract the method and call it.
This makes absolutely no sense.
If your two "math procedures" are different, then trying to call the thread-implementation from GUI would change the behaviour of your program. Conversely, if you can reuse the thread-implementation, then you most certainly can also extract the method! (Or at the very least the common elements.)
Caution
That said, there is some caution required when sharing code that might run in a TThread.Execute(). Any code that must run on the main thread needs to be synchronised or queued. Inside TThread objects, you'd simply call the Synchronize() or Queue() method. However, shared code shouldn't be on a TThread object making things a little trickier.
To resolve this, you can use the the Synchronize() and Queue() class methods on TThread. This allows you to synchronise without instantiating a TThread instance. (Note these methods are safe to call from the main thread because they would simply call the sync method directly in that case.)
Code along the following lines should do the trick.
Implement your shared code in a suitable object. This is conceptually a runnable object, and something you may want to research.
TSharedProcess = class
private
{ Set this if the process is run from a child thread,
leave nil if run from main thread. }
FThread: TThread;
procedure SyncProc();
public
procedure Run();
property Thread: TThread read FThread write FThread;
end;
procedure TSharedProcess.Run();
begin
...
TThread.Synchronize(FThread, SyncProc);
...
end;
When you want to run the shared code from the main thread, the following is an option.
begin
LProc := TSharedProcess.Create(...);
try
LProc.Run();
finally
LProc.Free;
end;
end;
To run from a child thread a simple thread wrapper will suffice. And then you can create the runnable object in the main thread, and pass it to the thread wrapper.
{ TShardProcessThread for use when calling from child thread. }
constructor TSharedProcessThread.Create(AProc: TSharedProcessThread);
begin
FProc := AProc;
FProc.Thread := Self;
inherited;
end;
procedure TShardProcessThread.Execute();
begin
FProc.Run();
end;
{ Main thread creates child thread }
begin
{ Keep reference to FProc because it can only be destroyed after
thread terminates.
TIP: Safest would be to use a reference counted interface. }
FProc := TSharedProcess.Create(...);
try
LThread := TShardProcessThread.Create(FProc);
LThread.OnTerminate := HandleThreadTerminate;
except
{ Exception in thread create means thread will not run and
will not terminate; so free object immediately. }
FProc.Free;
raise;
end;
end;
Disclaimer
I have not tested this code because I see no benefit in doing something like this. Users gain nothing by being able to force code to run on the main thread. Furthermore the paradigms for synchronous code are fundamentally different to asynchronous code. Trying to implement a hybrid reduces maintainability by cluttering your 'business code' with technical detail.
Use at your own risk.
The way to approach this problem is to extract into a method the code that you need to perform either in a worker thread or the main thread. You can then call that code either from your worker thread's Execute method, or from your main thread code.

Delphi and threads: "System Error. Code: 1400. Invalid window handle"

Being kinda new to threading I'm running into an issue:
I've built a small wrapper for Synapse THTTPSend object to handle Async calls through means of a thread. All seems to go well until I exit the application and get this error (using madExcept exceptions handler) "System Error. Code: 1400. Invalid window handle."
main thread ($2d00):
0047f931 +091 x.exe System.SysUtils RaiseLastOSError
0047f88e +00e x.exe System.SysUtils RaiseLastOSError
006198c4 +064 x.exe Vcl.Controls TWinControl.DestroyWindowHandle
0061674c +0dc x.exe Vcl.Controls TWinControl.Destroy
0067487b +05b x.exe Vcl.ComCtrls TTabSheet.Destroy
00616781 +111 x.exe Vcl.Controls TWinControl.Destroy
00673218 +0b8 x.exe Vcl.ComCtrls TCustomTabControl.Destroy
0067529c +06c x.exe Vcl.ComCtrls TPageControl.Destroy
00616781 +111 x.exe Vcl.Controls TWinControl.Destroy
0073d95e +06e x.exe Vcl.Forms TScrollingWinControl.Destroy
0073f5d2 +1e2 x.exe Vcl.Forms TCustomForm.Destroy
0040b2d5 +015 x.exe System TObject.Free
005a034e +08e x.exe System.Classes TComponent.DestroyComponents
0073be06 +046 x.exe Vcl.Forms DoneApplication
00472520 +030 x.exe System.SysUtils DoExitProc
0040e0d9 +079 x.exe System #Halt0
I've tracked this down to accessing a listview, it goes like this:
GUI calls a proc in my wrapper and assigns a callback method
Wrapper creates a thread and sets a callback
Thread does its job (http post) then calls the wrapper's callback
Wrapper's callback triggers another callback in the GUI which then updates some items in a listview
If I skip that listview part the error never happens, so I think something may be wrong in my thread code that messes up with the vcl/gui, probably cause it's still running while the VCL is being accessed? If I check the listview there is something very odd with it after the thread ends, sometimes the listview isn't even visible, or the added items aren't clickable.
Listview part
procedure Tx.AddLog(url,DelURL: string);
begin
if Settings.OptEnableLogging.Checked then begin
With UploadsForm.ListView1.Items.Add do begin
Caption := DateTimeToStr(Now);
SubItems.Add(OriginalFilename);
SubItems.Add(url);
SubItems.Add('');
SubItems.Add(DelURL);
end;
SaveLoggingLog;
end;
With UploadsForm.ListView2.Items.Add do begin
Caption := DateTimeToStr(Now);
SubItems.Add(OriginalFilename);
SubItems.Add(url);
SubItems.Add('');
SubItems.Add(DelURL);
end;
end;
The thread object
type
TMySynHTTPAsync = class(TThread)
protected
procedure Execute; override;
private
sObj: TSynHTTP;
public
Constructor Create(SynObj: TSynHTTP);
Destructor Destroy; override;
end;
implementation
Constructor TMySynHTTPAsync.Create(SynObj: TSynHTTP);
begin
inherited Create(False);
Self.FreeOnTerminate := True;
sObj := SynObj;
end;
Destructor TMySynHTTPAsync.Destroy;
begin
//
inherited Destroy;
end;
Procedure TMySynHTTPAsync.Execute;
begin
With sObj do begin
try
case tCallType of
thPostString: ThreadResult := sObj.Post(tURL, tPostVars);
end;
except
//
end;
if Assigned(sObj.xOnAsyncRequestDone) then sObj.xOnAsyncRequestDone;
FThread := nil;
end;
end;
creating the thread
FThread: TThread;
procedure TSynHTTP.DoAsync;
begin
ThreadResult := False;
FThread := TMySynHTTPAsync.Create(Self);
FThread.Resume;
end;
I'm guessing this is the culprit, as it goes through all the GUI processing before the thread finishes.
if Assigned(sObj.xOnAsyncRequestDone) then sObj.xOnAsyncRequestDone;
How could I solve this?
You have posted a lot of code but not the key, relevant part. Specifically the implementation of your xOnAsyncRequestDone event handler/method (unless it literally only calls that log method that you posted).
This method is being executed in the context of the TMySynHTTPAsync thread and based on the behaviour you are describing - particularly the fact that Synchronize resolves your problem - it is highly likely that some activity in that event handler is creating a window handle.
That window handle is then owned by the HTTP Async thread, not the main application thread (sometimes referred to as the "VCL thread") that is otherwise running your application. When your application closes, the VCL thread performs some final housekeeping, destroying objects and windows etc. if one of those windows was created by some other thread this will cause problems.
Window handles are the strict property of the thread in which they were created. You cannot create a window handle in one thread and then destroy it in another.
NOTE: This is a fundamental of Windows, not Delphi.
Worth noting here is that window handles in VCL can often be created indirectly. You won't necessarily see an explicit creation of a control that marks the creation of the underlying window handle. It is quite common for window handles only to be actually created when required. Similarly changing the properties of a control can trigger the VCL to attempt to recreate the window for that control, destroying the current one in the process.
It should be fairly apparent that these mechanisms are highly vulnerable to problems that can arise when VCL methods are called by threads other than the VCL thread. This is why you will often here it said that "the VCL is not thread-safe".
The safest way to operate is to only manipulate VCL objects from code running in the VCL thread itself.
Synchronize to the Rescue
This is in fact precisely why Synchronize exists.
The mechanism that you are invoking by using Synchronize actually works to ensure that the method you are Synchronizing is execute on the VCL thread. If this is in fact creating a window handle then when the VCL thread later comes to destroy that window handle it is quite free to do so since it did in fact create it.
Hence your problem is solved.
Other Options
The Synchronize mechanism is quite complex however, dealing (these days) with cross platform concerns among other things, and as a result may be overkill in this case.
If your code is specific to Windows, a possible alternate solution to this problem may be to exploit the fact that windows allows threads to send (or post) messages to windows in other threads. When those messages are received by those windows, they are then processed by that window's own thread just as all other messages to those windows are. i.e. you cannot end up interrupting a "click" message received by that window by suddenly jumping across to run the notification from the thread. That notification message simply has to wait it's turn while the window finishes processing that click message. For example.
You can think of this as a 'Synchronize' system "built-in" in to the OS.
So you could, for example, pass a window handle to a form (or control or anything with a window handle) to your HTTP async thread during initialisation, identifying a VCL window that wishes to receive the "request complete" or other notifications from the thread. The thread can then send notifications to that window handle using PostMessage or SendMessage which you could handle either by overriding the WindowProc on the form or using a declared message handler.
If the thread uses SendMessage() to send the notification, then it is automatically suspended and forced to wait until the message is received and processed by the window (in the VCL thread).
If the thread uses PostMessage() then the message is sent asynchronously and the thread can continue with other work without having to wait. The VCL thread will eventually pick up the message and process it.
NOT a Recommendation
This is not to say that I would recommend this alternative in this case. Although it does seem that it might be appropriate given that it does appear to be a simple "work is complete" notification in this case, without a more comprehensive understanding of your specific needs it is impossible to say which is most appropriate.
I mention it only to highlight the fact that alternatives do exist and that the key to safe, reliable threading is to understand the principles and the mechanisms involved.
The golden rule with threading is to not touch the GUI from another thread.
Depending on the situation this can be solved with Synchronize(), posting messages async (PostMessage()) or synchronized (SendMessage()). Another asynchronic option is using the TThread.Queue() call.
Last but not least, if you want to notify the GUI that the thread is done, assign an OnTerminate event handler to the thread. This event is executed in the main thread when the thread finishes executing.
This is an example how it could be implemented:
type
TMySynHTTPAsync = class(TThread)
protected
procedure Execute; override;
private
sObj: TSynHTTP;
procedure MyTerminateHandler(Sender: TObject);
public
Constructor Create(SynObj: TSynHTTP);
Destructor Destroy; override;
end;
procedure TMySynHTTPAsync.MyTerminateHandler(Sender: TObject);
begin // Executed in the main thread
if Assigned(sObj) and Assigned(sObj.xOnRequestDone) then sObj.xOnRequestDone;
end;
procedure TMySynHTTPAsync.Execute;
begin
Self.OnTerminate := MyTerminateHandler; // Assign the OnTerminate event handler
...
end;
Synchronize(sObj.xOnAsyncRequestDone) seems to solve the issue.

When do I need to call CoInitialize() in this scenario?

I'm building a multi-threaded windows service application in Delphi XE2 which uses ADO database components to connect to SQL Server. I've used CoInitialize(nil); plenty times before inside threads, but in this case, I have a function which I'm unsure about.
This function is called TryConnect which attempts to connect to a database with a given connection string. It returns true or false on the connection success. The problem is that this function will be used both inside and outside the main service thread, and it will be creating its own temporary TADOConnection component, which requires CoInitialize...
My question is do I need to call CoInitialize inside this function also? If I do, and since the service's execute procedure uses CoInitialize also, will they interfere if I call this function from within the service? The TryConnect function is inside of an object which is created from the main service thread (but will eventually be moved to its own thread). I need to know if calling CoInitialize() twice from the same thread (and CoUninitialize) will interfere - and how to handle this scenario properly.
Here's the code below...
//This is the service app's execute procedure
procedure TJDRMSvr.ServiceExecute(Sender: TService);
begin
try
CoInitialize(nil);
Startup;
try
while not Terminated do begin
DoSomeWork;
ServiceThread.ProcessRequests(False);
end;
finally
Cleanup;
CoUninitialize;
end;
except
on e: exception do begin
PostLog('EXCEPTION in Execute: '+e.Message);
end;
end;
end;
//TryConnect might be called from same service thread and another thread
function TDBPool.TryConnect(const AConnStr: String): Bool;
var
DB: TADOConnection; //Do I need CoInitialize in this function?
begin
Result:= False;
DB:= TADOConnection.Create(nil);
try
DB.LoginPrompt:= False;
DB.ConnectionString:= AConnStr;
try
DB.Connected:= True;
Result:= True;
except
on e: exception do begin
end;
end;
DB.Connected:= False;
finally
DB.Free;
end;
end;
So to clarify what it's really doing, I might have an occasion of this:
CoInitialize(nil);
try
CoInitialize(nil);
try
//Do some ADO work
finally
CoUninitialize;
end;
finally
CoUninitialize;
end;
CoInitialize has to be called in every single thread that uses COM, regardless of what thread it is, or whether it has a parent thread or child threads. If the thread uses COM, it must call CoInitialize.
The correct answer here is "it depends". Since you know the service thread has called CoInitialize, if TryConnect is called from the service thread it won't need to be called again. If the other threads that could call it have also called CoInitialize, it won't need to be called, as the function will run under the calling thread.
The MSDN documentation specifically addresses this question (emphasis added):
Typically, the COM library is initialized on a thread only once. Subsequent calls to CoInitialize or CoInitializeEx on the same thread will succeed, as long as they do not attempt to change the concurrency model, but will return S_FALSE. To close the COM library gracefully, each successful call to CoInitialize or CoInitializeEx, including those that return S_FALSE, must be balanced by a corresponding call to CoUninitialize. However, the first thread in the application that calls CoInitialize with 0 (or CoInitializeEx with COINIT_APARTMENTTHREADED) must be the last thread to call CoUninitialize. Otherwise, subsequent calls to CoInitialize on the STA will fail and the application will not work.
So the answer is: If you're not sure, call CoInitialize. Do it in a try..finally block, and call CoUnitialize in the finally, or initialize in a constructor and uninitialize in the destructor.
Your service thread should really not be doing any work at all. It should be used exclusively for responding to Service Manager calls. The OnExecute or OnStart/OnStop of the service should control the instantiation and execution of a "MainWorkThread" that represents the functionality of your service. See https://stackoverflow.com/a/5748495/11225 for an example.
The main work thread could do the real work and/or delegate it to other threads. Each thread that could use COM should have the CoInitialize/CoUninitialize calls and the easiest way to achieve that is to code them in the outer most try finally block of the thread's (overridden) Execute method.
TDBPool or any other class using COM should not concern itself with the CoInitialize and CoUninitialize calls. These methods need to be called in every thread that could use COM and a class doesn't and shouldn't know in which thread it will be executed.

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.

Resources