Search code examples
delphidelphi-7

How would one pass a dynamic property as a parameter to a function?


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;

Solution

  • 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;