-5

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?

asd-tm
  • 3,381
  • 2
  • 24
  • 41
  • 3
    Put maths in a separate procedure and call it from both places! You are thinking the wrong way round. – Dsm Feb 06 '18 at 08:51
  • Thank you @Dsm This way is obvious. However as I sated in my Q I would like to avoid it. Is it theoretically possible to call Execute from GUI thread? – asd-tm Feb 06 '18 at 09:00
  • 3
    @asd-tm no it is not. You need to extract your math code into a function that can be called in any thread that needs it. You say you want to avoid that but you don't say why. But it is the correct solution – Remy Lebeau Feb 06 '18 at 10:01

2 Answers2

2

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:

  1. If you don't need a thread, there's absolutely no point in starting a thread that you don't want to use.
  2. You would need to prevent Execute() from doing any processing on its thread-run.
  3. You could then call Execute from the main thread.
  4. 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.

Disillusioned
  • 14,635
  • 3
  • 43
  • 77
  • :D Hopefully I persuaded OP of the folly of this approach. It was an interesting exercise in double-checking certain details of `TThread` implementation though. – Disillusioned Feb 06 '18 at 16:08
  • I guessed that OP's slight difference between "GUI math" and "thread math" might be related to synchronisation required on the thread implementations. That would explain why OP was happy to reuse it, but didn't see a way to extract it. (Typical XY problem.) That's what got me thinking about how to have the extracted code able to synchronise from main thread or child threads. It was an interesting challenge from that perspective; but still something I wouldn't do. – Disillusioned Feb 06 '18 at 16:21
1

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thank you David for your reply. But as I stated in my question I would like to avoid this way for the following reasons: the procedures slightly differ when called from within the thread and GUI thread. For instance I interact with GUI controls in different ways, check if the user aborts the task etc. In other words. Is it theoretically possible to call the predesigned TThread.Execute in GUI thread? – asd-tm Feb 06 '18 at 08:58
  • The code uses different variables in the two different methods. So the step `evaluating and assigning the best result` is omitted for instance. – asd-tm Feb 06 '18 at 09:04
  • 3
    That makes no sense at all. You can absolutely do it the way I said, and that is the correct way to do it. – David Heffernan Feb 06 '18 at 09:07
  • 1
    I don't know why the question was downvoted, any more than anybody knows who voted – David Heffernan Feb 06 '18 at 09:15
  • But still. Is it possible to run TThread.Execute method in GUI without code extraction etc. I am just curious. – asd-tm Feb 06 '18 at 09:18
  • 1
    No it is not. And it is trivial to extract all the code in the `Execute` method. I think this is done here. – David Heffernan Feb 06 '18 at 09:19
  • 3
    @asd-tm simply extract the code into a function, and pass it a parameter indicating which kind of thread is calling it, or have it compare `GetCurrentThreadId` to `MainThreadID`, so it can adjust its logic accordingly. Provide the function with a callback it can call periodically to check abort status, and then use different callbacks for different types of threads. Pass the function references to variables for it to act on. And so on. There are ways to make the function do different things in different threads without actually being tied to any particular thread. – Remy Lebeau Feb 06 '18 at 10:04