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:
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.
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.
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:
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).