Say I have an object, in my case a TThread, and I want to check one of it's properties dynamically in a function, in this case TThread.Terminated.
Is there a clean way to pass the 'property' such that I am actually passing the getter function and can therefor check the value of that property dynamically?
More detail:
My specific issue is that I am implementing threading in Delphi for the first time. I am not a fan of the fact that the standard Delphi Threading implementation seems to be to create a custom thread class to execute the method you want to thread. I found a satisfactory solution to this annoyance, where one uses a delegate to define the method to execute and then passes the actual method into the thread object at creation, allowing better separation/encapsulation of the threading code and the method to be executed by the thread. Details below.
However the implementation of the above idea I found uses TThread.Suspend to allow early termination of the method being executed. The Delphi docs mark TThread.Suspend as deprecated, and instead suggest long running methods should check the Terminated property, in order to allow early termination when requested by the parent thread. Thus I need a way to pass a reference to TThread.Terminated to the method it is executing so that that method can implement it's own early termination.
I can't pass the property by reference using the var syntax. I can't pass the underlying FTerminated field by reference, as it is a private member of TThread. I know I can just pass a reference to the TThread object into the method, but that type of circular referencing seems like a hacky way of getting around the problem.
So, is there a 'accepted' way to dynamically pass a property, or am I stuck with my current method?
Current Code: interface
uses
Windows, Messages, Classes, Forms;
type
// Method Thread Will Execute. Acts on Data. Runs in RunningThread.
// (Reference to RunningThread is past so method can check runningthread.terminated
// to allow long running methods to terminate early when parent thread requests it)
TThreadMethod = procedure (Data: pointer; RunningThread: TThread) of object;
//A Simple Thread Which Excecutes a Method on Some Data
TSimpleThread = class(TThread)
protected
Method: TThreadMethod;
Data: pointer;
procedure Execute; override;
public
constructor Create(Method: TThreadMethod; Data: pointer; CreateSuspended: boolean); overload;
end;
implementation
// SimpleThread
constructor TSimpleThread.Create(Method: TThreadMethod;
Data: pointer;
CreateSuspended: boolean );
begin
Self.Method := Method;
Self.Data := Data;
inherited Create(CreateSuspended);
Self.FreeOnTerminate := True;
end;
procedure TSimpleThread.Execute();
begin
Self.Method(Data, Self);
end;
Say I have an object, in my case a TThread, and I want to check one of it's properties dynamically in a function, in this case TThread.Terminated.
The best way to handle that is to derive a new class from TThread
and override its virtual Execute()
method to run your thread code directly, and then that code can access the object's Terminated
property when needed. Just as your example is doing.
If your thread code cannot be put in Execute()
directly (say, you are using TThread.CreateAnonymousThread()
or TTask
instead), where you can't pass the original TThread
object pointer directly to the thread procedure, all is not lost. TThread
has a CurrentThread
class property to access the TThread
object that is running the calling code. When TThread
starts running, it stores its Self
pointer in a thread
variable (so each thread context gets its own copy of the variable) which the CurrentThread
property then accesses.
However, your question is marked Delphi 7, which does not support anonymous procedures, anonymous threads, or tasks, so you are basically stuck manually passing your TThread
object to your procedure code. You could use your own thread
variable if you don't want to pass the object pointer as a procedure parameter.
Is there a clean way to pass the 'property' such that I am actually passing the getter function and can therefor check the value of that property dynamically?
For starters, there is no getter function, the Terminated
property directs returns the FTerminated
member, which is private
anyway. You need the actual TThread
object pointer in order to read from the Terminated
property.
The only way to pass around a pointer/reference to the FTerminated
member itself is to use Extended RTTI, which does not exist in Delphi 7. So that does not help you. The old RTTI in Delphi 7 is not rich enough to access the FTerminated
member.
However the implementation of the above idea I found uses
TThread.Suspend
to allow early termination of the method being executed.
Then you are working with a bad implementation.
Unless you are referring to the ACreateSuspended
parameter of the TThread
constructor, which is perfectly safe to use. If it is set to True, the thread is not created and then suspended, it is created in a suspended state to begin with. You would simply call Resume()
(or Start()
in later Delphi versions) when you are ready to start the thread after creating it.
Otherwise, just set ACreateSuspended
to False, and let it auto-start itself after all constructors have exited.
The Delphi docs mark TThread.Suspend as deprecated
That is correct. Suspend()
is never safe to use from outside of the thread, as other threads cannot ensure the thread is in a safe state to be suspended. Suspend()
is only safe to use if a thread suspends itself from inside its own Execute()
method, since only that thread would know when it is safe to perform suspension. But even then, there are usually better ways to implement thread suspension without actually using Suspend()
(like waiting on a TEvent
instead).
Thus I need a way to pass a reference to TThread.Terminated to the method it is executing so that that method can implement it's own early termination.
No, you need to pass a reference to the TThread
object itself to your procedure, not a reference to any particular property.
I can't pass the property by reference using the var syntax. I can't pass the underlying FTerminated field by reference, as it is a private member of TThread.
Correct.
I know I can just pass a reference to the TThread object into the method, but that type of circular referencing seems like a hacky way of getting around the problem.
It is the only way, if you want to use the TThread.Terminated
property.
Otherwise, simply don't use TThread.Terminated
at all. Use you own signaling mechanism instead. The Terminated
property is just a Boolean
flag, nothing more. It is provided as a convenience, not a requirement. You can use whatever you want to signal your procedure to exit and the thread to terminate itself.
So, is there a 'accepted' way to dynamically pass a property, or am I stuck with my current method?
Your current method is basically what you have to do, especially in Delphi 7. For later Delphi versions, you are basically re-creating what TThread.CreateAnonymousThread()
does, eg:
TThread.CreateAnonymousThread(
procedure
begin
MyMethod(MyData, TThread.CurrentThread);
end
).Start;