Recent

Author Topic: Best practice for a multi-platform worker thread  (Read 8943 times)

palacs

  • New Member
  • *
  • Posts: 21
Best practice for a multi-platform worker thread
« on: July 13, 2018, 09:08:31 am »
I'd like to have a worker TThread which I utilize by sending messages from the main (UI) thread so it should be in a blocked state waiting for messages then if received, start some kind of processing according to the received message type. When done, it should send a message back to the main thread (UI) which will then display results. I was experimenting with PostMessage but the only part I could achieve is sending a message to the UI thread to display results.

Code: [Select]
unit threadtest;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  LMessages, LCLIntf;

const
  UM_RESULT = WM_USER + 1;

type
  TWorker = class(TThread)
    private
      textbuffer: String;
    protected
      procedure Execute; override;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    worker: TWorker;
  protected
    procedure UMResult(var Message: TLMessage); message UM_RESULT;
  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
begin
  worker := TWorker.Create(False);
end;

procedure TForm1.UMResult(var Message: TLMessage);
begin
  Memo1.Lines.Add('Message: ' + String(Message.WParam));
end;

procedure TWorker.Execute;
begin
  // Imitate to do something
  Sleep(1000);

  // Send message to UI thread
  textbuffer := DateTimeToStr(Now);
  LCLIntf.PostMessage(Form1.Handle, UM_RESULT, PtrInt(PString(textbuffer)), 0);
end;

end.

What is the best basic cross-platform workflow to do this?

How should I write a while not Terminated ... loop into TWorker.Execute that would catch messages and call functions within TWorker? Delphi tutorials talk about GetMessage but I didn't find them anywhere in LCLIntf.

schuler

  • Full Member
  • ***
  • Posts: 223
Re: Best practice for a multi-platform worker thread
« Reply #1 on: July 13, 2018, 09:45:12 am »
@palacs.
Your question is in my interest. I've had horrible experiences in Linux in regards to this (although I generally like Linux).

Anyway, some of my difficulties have been posted here:
https://forum.lazarus.freepascal.org/index.php/topic,41477.0.html


palacs

  • New Member
  • *
  • Posts: 21
Re: Best practice for a multi-platform worker thread
« Reply #2 on: July 14, 2018, 02:08:14 pm »
I just can't believe that this is so hard and there is no simple pattern. Communicating with a thread should be a basic feature in GUI programming, what Lazarus is for.

balazsszekely

  • Guest
Re: Best practice for a multi-platform worker thread
« Reply #3 on: July 14, 2018, 04:16:32 pm »
@palacs
Quote
I just can't believe that this is so hard and there is no simple pattern. Communicating with a thread should be a basic feature in GUI programming, what Lazarus is for.
It is a basic feature. Please test attached project. Count is just a dummy method, you should replace it with something useful. Feel free to ask more questions if you like.
Code: Pascal  [Select][+][-]
  1. unit uThread;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils;
  9.  
  10. type
  11.   TThreadStatus = (tsCount, tsIDLE);  //extend this according to your needs
  12.  
  13.   TOnBeginCount = procedure(Sender: TObject; AMsg: String) of object;
  14.   TOnCount = procedure(Sender: TObject; ATotCnt, ACur: Integer) of object;
  15.   TOnEndCount = procedure(Sender: TObject; AMsg: String) of object;
  16.  
  17.   { TWorkerThread }
  18.   TWorkerThread = class(TThread)
  19.   private
  20.     FThreadStatus: TThreadStatus;
  21.     FOnBeginCount: TOnBeginCount;
  22.     FOnCount: TOnCount;
  23.     FOnEndCount: TOnEndCount;
  24.     FTotCnt: Integer;
  25.     FCur: Integer;
  26.     procedure Count;
  27.     procedure DoBeginCount;
  28.     procedure DoCount;
  29.     procedure DoEndCount;
  30.   protected
  31.     procedure Execute; override;
  32.   public
  33.     constructor Create;
  34.     destructor Destroy; override;
  35.   public
  36.     property ThreadStatus: TThreadStatus read FThreadStatus write FThreadStatus;
  37.     property OnBeginCount: TOnBeginCount read FOnBeginCount write FOnBeginCount;
  38.     property OnCount: TOnCount read FOnCount write FOnCount;
  39.     property OnEndCount: TOnEndCount read FOnEndCount write FOnEndCount;
  40.   end;
  41.  
  42. implementation
  43.  
  44. { TWorkerThread }
  45.  
  46. procedure TWorkerThread.DoCount;
  47. begin
  48.   if Assigned(FOnCount) then
  49.     FOnCount(Self, FTotCnt, FCur);
  50. end;
  51.  
  52. procedure TWorkerThread.DoBeginCount;
  53. begin
  54.   if Assigned(FOnBeginCount) then
  55.     FOnBeginCount(Self, 'Worker thread: I''m gonna count to 100.');
  56. end;
  57.  
  58. procedure TWorkerThread.Count;
  59. var
  60.   I: Integer;
  61. begin
  62.   Synchronize(@DoBeginCount);
  63.   try
  64.     //replace this part with something useful
  65.     FTotCnt := 100;
  66.     for I := 0 to FTotCnt do
  67.     begin
  68.       FCur := I;
  69.       Synchronize(@DoCount);
  70.       Sleep(50);
  71.     end;
  72.   finally
  73.     FThreadStatus := tsIDLE;
  74.     Synchronize(@DoEndCount);
  75.   end;
  76. end;
  77.  
  78. procedure TWorkerThread.DoEndCount;
  79. begin
  80.   if Assigned(FOnEndCount) then
  81.     FOnEndCount(Self, 'Worker thread: End count, status is IDLE.');
  82. end;
  83.  
  84. procedure TWorkerThread.Execute;
  85. begin
  86.   while not Terminated do
  87.   begin
  88.     case FThreadStatus of
  89.       tsCount: Count;
  90.       tsIDLE: Sleep(50);
  91.     end;
  92.   end;
  93. end;
  94.  
  95. constructor TWorkerThread.Create;
  96. begin
  97.   inherited Create(True);
  98.   FreeOnTerminate := True;
  99.   FThreadStatus := tsIDLE;
  100. end;
  101.  
  102. destructor TWorkerThread.Destroy;
  103. begin
  104.   inherited Destroy;
  105. end;
  106.  
  107. end.
« Last Edit: July 14, 2018, 04:39:18 pm by GetMem »

BeniBela

  • Hero Member
  • *****
  • Posts: 905
    • homepage
Re: Best practice for a multi-platform worker thread
« Reply #4 on: July 14, 2018, 05:39:10 pm »
Synchronize only works with a main thread, does it not?

I never could get it to work on Android

Thaddy

  • Hero Member
  • *****
  • Posts: 14201
  • Probably until I exterminate Putin.
Re: Best practice for a multi-platform worker thread
« Reply #5 on: July 14, 2018, 06:29:17 pm »
Synchronize only works with a main thread, does it not?

I never could get it to work on Android
Why? synchronize is between threads, not only the main thread. Or it should be...
Specialize a type, not a var.

balazsszekely

  • Guest
Re: Best practice for a multi-platform worker thread
« Reply #6 on: July 14, 2018, 06:45:48 pm »
@BeniBela
Quote
Synchronize only works with a main thread, does it not?
Yes.

@Thaddy
Quote
Why? synchronize is between threads, not only the main thread.
No. Is between a worker and the main thread. The AMethod specified in the parameter is called in the context of the main thread. Synchronize is blocking, the thread is waiting until the code is executed in the main thread. For asynchronous processing you can use Queue.

PS: Synchronize /Queue is only needed when you wish to update visual controls.

Thaddy

  • Hero Member
  • *****
  • Posts: 14201
  • Probably until I exterminate Putin.
Re: Best practice for a multi-platform worker thread
« Reply #7 on: July 14, 2018, 07:08:11 pm »
@Thaddy
Quote
Why? synchronize is between threads, not only the main thread.
No. Is between a worker and the main thread. The AMethod specified in the parameter is called in the context of the main thread. Synchronize is blocking, the thread is waiting until the code is executed in the main thread. For asynchronous processing you can use Queue.

PS: Synchronize /Queue is only needed when you wish to update visual controls.
Not it is NOT. Basics. It is about the initiating thread - that happens to be often the main thread...... This is silly..If you missed that?  back to school <grumpyish  :D>
I have trees of threads running...
Specialize a type, not a var.


balazsszekely

  • Guest
Re: Best practice for a multi-platform worker thread
« Reply #9 on: July 14, 2018, 07:30:24 pm »
@Thaddy
Quote
Not it is NOT. Basics. It is about the initiating thread - that happens to be often the main thread...... This is silly..If you missed that?  back to school <grumpyish  :D>
I have trees of threads running...
Too much talk is a waste of time, at least this is how I see it. Luckily everyone who is interested in the subject can do a quick google search.
« Last Edit: July 14, 2018, 07:37:53 pm by GetMem »

mercurhyo

  • Full Member
  • ***
  • Posts: 242
Re: Best practice for a multi-platform worker thread
« Reply #10 on: July 14, 2018, 11:23:02 pm »
https://forum.lazarus.freepascal.org/index.php/topic,41802.0.html

do not use messages outside main LCL thread because they are not crossplatform.

use 'Post a Method' and the Done boolean variable to test, or the Callback I designed to communicate
« Last Edit: July 14, 2018, 11:32:38 pm by mercurhyo »
DEO MERCHVRIO - Linux, Win10pro - Ryzen9XT 24threads + Geforce Rtx 3080SUPRIM
god of financial gain, commerce, eloquence (and thus poetry), messages, communication (including divination), travelers, boundaries, luck, trickery and thieves; he also serves as the guide of souls to the underworld

mercurhyo

  • Full Member
  • ***
  • Posts: 242
Re: Best practice for a multi-platform worker thread
« Reply #11 on: July 14, 2018, 11:43:47 pm »
you just need to learn how to use 'array of const', then use my TPowerThread or TThreadedComponent from version 0.5
DEO MERCHVRIO - Linux, Win10pro - Ryzen9XT 24threads + Geforce Rtx 3080SUPRIM
god of financial gain, commerce, eloquence (and thus poetry), messages, communication (including divination), travelers, boundaries, luck, trickery and thieves; he also serves as the guide of souls to the underworld

mercurhyo

  • Full Member
  • ***
  • Posts: 242
DEO MERCHVRIO - Linux, Win10pro - Ryzen9XT 24threads + Geforce Rtx 3080SUPRIM
god of financial gain, commerce, eloquence (and thus poetry), messages, communication (including divination), travelers, boundaries, luck, trickery and thieves; he also serves as the guide of souls to the underworld

wadman

  • New Member
  • *
  • Posts: 37
    • wadman's home
Re: Best practice for a multi-platform worker thread
« Reply #13 on: July 16, 2018, 08:30:01 am »
In my solution, an additional thread is used to emulate the message queue.



You can see here: https://github.com/wadman/wthread
Unfortunately, some of the comments are in russian.

mercurhyo

  • Full Member
  • ***
  • Posts: 242
Re: Best practice for a multi-platform worker thread
« Reply #14 on: July 16, 2018, 02:19:04 pm »
In my solution, an additional thread is used to emulate the message queue.



You can see here: https://github.com/wadman/wthread
Unfortunately, some of the comments are in russian.

AS I mentioned here, WMessages are NOT crossplatform. As I wrote elsewhere, your WThread package is fine on windows/Delphi but have some issues on other platforms. That is even the reason which gave me the need to write my own TPowerThread.
https://forum.lazarus.freepascal.org/index.php/topic,41802.0.html
1) your WThread uses WMessage (not adviced on other platforms but windows)
2) you WThread loves variants, they are SLOW and disturb the gain around multithreading
3) why use a complicated solution when a simplier one can be efficient?
« Last Edit: July 16, 2018, 02:27:02 pm by mercurhyo »
DEO MERCHVRIO - Linux, Win10pro - Ryzen9XT 24threads + Geforce Rtx 3080SUPRIM
god of financial gain, commerce, eloquence (and thus poetry), messages, communication (including divination), travelers, boundaries, luck, trickery and thieves; he also serves as the guide of souls to the underworld

 

TinyPortal © 2005-2018