Recent

Author Topic: How interact with UI controls in a thread (started with BeginThread)?  (Read 1700 times)

ssawgift

  • New Member
  • *
  • Posts: 48
    • My Personal Website
After some digging in docs and wiki, I understand TThread has Synchronize method to update UI in main thread. The only problem is that I don't want to use TThread because it's an overkill for my purpose.

On Windows platform, I usually send/post a message to a window and in the message handler I process the message and update UI accordingly. But how do I do that in Lazarus?

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11383
  • FPC developer.
Re: How interact with UI controls in a thread (started with BeginThread)?
« Reply #1 on: November 28, 2022, 07:36:04 am »
Easiest is to look up some Delphi example, but roughly define a message handler in your class.

Be careful you call the right methods (in windows or messages unit), and not the Lazarus methods that emulate some message dispatch methods for internal use.

See https://www.freepascal.org/docs-html/ref/refsu31.html  and a gazillion Delphi examples on the web.

But I think micromanaging this is overkill. TThread doesn't really have any serious overhead.

ssawgift

  • New Member
  • *
  • Posts: 48
    • My Personal Website
Re: How interact with UI controls in a thread (started with BeginThread)?
« Reply #2 on: November 28, 2022, 09:24:44 am »
I know TThread is easy to manage but I just want to practise my skills on Free Pascal.

Code: Pascal  [Select][+][-]
  1. // declaration
  2. class function FindInfFile(p: pointer): PtrInt;
  3.  
  4. // in button click event handler
  5. m_workerID := BeginThread(@TMainForm.FindInfFile, self);
  6.  
I get this error:

MainForm.pas(187,51) Error: Incompatible type for arg no. 1: Got "<class method type of function(Pointer):LongInt of object;Register>", expected "<procedure variable type of function(Pointer):LongInt;Register>"

I assumed that by declaring a class/static procedure, it should match BeginThread signature, but apprently I am wrong. What's wrong with my syntax?

BTW, I know that I can declare a top level bare procedure for this, but I am just curious why a class procedure won't do the work.

Easiest is to look up some Delphi example, but roughly define a message handler in your class.

Be careful you call the right methods (in windows or messages unit), and not the Lazarus methods that emulate some message dispatch methods for internal use.

See https://www.freepascal.org/docs-html/ref/refsu31.html  and a gazillion Delphi examples on the web.

But I think micromanaging this is overkill. TThread doesn't really have any serious overhead.

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11383
  • FPC developer.
Re: How interact with UI controls in a thread (started with BeginThread)?
« Reply #3 on: November 28, 2022, 09:37:30 am »
I know TThread is easy to manage but I just want to practise my skills on Free Pascal.

IMHO this is not the subject for that if you struggle with base language constructs, but anyway:
Quote

MainForm.pas(187,51) Error: Incompatible type for arg no. 1: Got "<class method type of function(Pointer):LongInt of object;Register>", expected "<procedure variable type of function(Pointer):LongInt;Register>"
[/tt]
I assumed that by declaring a class/static procedure, it should match BeginThread signature, but apprently I am wrong. What's wrong with my syntax?

So why did you assume that ?  The definition of the beginthread parameter doesn't say anything like that?
 

avra

  • Hero Member
  • *****
  • Posts: 2514
    • Additional info
Re: How interact with UI controls in a thread (started with BeginThread)?
« Reply #4 on: November 28, 2022, 09:38:42 am »
Updating GUI elements from the thread is easy when QueueAsyncCall is used, so keep that in mind if you reconsider the thread idea. Unfortunately there are not many examples out there, so here is a nice one to transfer whole record to the main thread:
https://forum.lazarus.freepascal.org/index.php/topic,38851.msg265181.html#msg265181

Good thing is that QueueAsyncCall is non-blocking and it does very usefull serialization so there is no need for syncing at all. For example, that feature was heavily used in MultiLog where many threads update memo on a form and also write to the same log file without any problem. The only drawback I see is Delphi incompatibility.

Here is a wiki article on using QueueAsyncCall:
https://wiki.freepascal.org/Asynchronous_Calls
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

ssawgift

  • New Member
  • *
  • Posts: 48
    • My Personal Website
Re: How interact with UI controls in a thread (started with BeginThread)?
« Reply #5 on: November 28, 2022, 01:57:52 pm »
I successfully get both asynccall and user message working. But I get warnings when I convert record pointer to PtrInt:

Form1.pas(242,47) Hint: Conversion between ordinals and pointers is not portable

Code: Pascal  [Select][+][-]
  1.   AsyncCallRecord = record
  2.     timestamp: TDateTime;
  3.     tag: string;
  4.   end;
  5.   AsyncCallRecordPtr = ^AsyncCallRecord;
  6.  
  7. //=======================
  8. function WorkerProc(data: pointer): PtrInt;
  9. var
  10.   this: TForm1;
  11.   asyncdata: AsyncCallRecordPtr;
  12. begin
  13.   result := 0;
  14.   this := TForm1(data);
  15.   asyncdata := new(AsyncCallRecordPtr);
  16.   asyncdata^.tag := 'Async call from worker proc';
  17.   asyncdata^.timestamp := now;
  18.   Application.QueueAsyncCall(@this.AsyncProc, PtrInt(asyncdata));
  19.   PostMessage(this.Handle, LM_USER + 1, 12345, 567890);
  20. end;
  21.  
I am sure this is actually safe because it is what I do in WIN32 SDK programming (casting WPARM/LPARAM to various data types). But is it possible to eliminate this warning?

Thaddy

  • Hero Member
  • *****
  • Posts: 14205
  • Probably until I exterminate Putin.
Re: How interact with UI controls in a thread (started with BeginThread)?
« Reply #6 on: November 28, 2022, 02:04:22 pm »
A hint is not a warning. You can simple declare {$HINTS OFF}
(Although in this case the hint should be a warning..... Platform)
You can even turn individual hints off using {$WARN <hint number> OFF}
Specialize a type, not a var.

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2007
  • Fifty shades of code.
    • Delphi & FreePascal
Re: How interact with UI controls in a thread (started with BeginThread)?
« Reply #7 on: November 28, 2022, 02:09:31 pm »
A hint is not a warning. You can simple declare {$HINTS OFF}
(Although in this case the hint should be a warning..... Platform)
You can even turn individual hints off using {$WARN <hint number> OFF}
Or press in the Messages window with mouse right click on a specific hint/warning to suppress it over a menu (that will add conditional compiling for that line of code)
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

ssawgift

  • New Member
  • *
  • Posts: 48
    • My Personal Website
Re: How interact with UI controls in a thread (started with BeginThread)?
« Reply #8 on: November 28, 2022, 02:40:16 pm »
Wow, I did not even know you can do that in Lazaru IDE. I usually use "#pragma disable" in C/C++ code to suppress some known safe warnings.
A hint is not a warning. You can simple declare {$HINTS OFF}
(Although in this case the hint should be a warning..... Platform)
You can even turn individual hints off using {$WARN <hint number> OFF}
Or press in the Messages window with mouse right click on a specific hint/warning to suppress it over a menu (that will add conditional compiling for that line of code)

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2007
  • Fifty shades of code.
    • Delphi & FreePascal
Re: How interact with UI controls in a thread (started with BeginThread)?
« Reply #9 on: November 28, 2022, 03:42:39 pm »
Wow, I did not even know you can do that in Lazaru IDE.
Oh yes, the IDE offers a lot of comfort  :-*
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

avra

  • Hero Member
  • *****
  • Posts: 2514
    • Additional info
Re: How interact with UI controls in a thread (started with BeginThread)?
« Reply #10 on: December 02, 2022, 09:57:26 am »
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

alpine

  • Hero Member
  • *****
  • Posts: 1038
Re: How interact with UI controls in a thread (started with BeginThread)?
« Reply #11 on: December 02, 2022, 10:24:34 am »
I successfully get both asynccall and user message working. But I get warnings when I convert record pointer to PtrInt:
*snip*

You should be aware that AsyncCalls can do any sort of nasty things thanks to some dangling references ... since we're talking about windows (this.Handle) ...
Messages don't have that trouble.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

ssawgift

  • New Member
  • *
  • Posts: 48
    • My Personal Website
Re: How interact with UI controls in a thread (started with BeginThread)?
« Reply #12 on: December 03, 2022, 07:52:03 am »
I am very familiar with WIN32 GUI programming, but I am not able to understand what you mean. As long as you manipulate HANDLE on the main thread, there is no problem at all. Can you give some detailed examples?
I successfully get both asynccall and user message working. But I get warnings when I convert record pointer to PtrInt:
*snip*

You should be aware that AsyncCalls can do any sort of nasty things thanks to some dangling references ... since we're talking about windows (this.Handle) ...
Messages don't have that trouble.

alpine

  • Hero Member
  • *****
  • Posts: 1038
Re: How interact with UI controls in a thread (started with BeginThread)?
« Reply #13 on: December 03, 2022, 10:31:01 am »
Well, since the Application.AsyncCall() gets a TDataEvent parameter, which is 'of object', usually it contains an internal reference to a TForm. That handler (the parameter) will be invoked in the future, the form may be non-existent, i.e. the internal reference will be dangling.

To prevent this, it isn't enough just to stop firing AsyncCall's towards that form, you should also remove the already pending:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormDestroy(Sender: TObject);
  2. begin
  3.   // Remove pending async calls
  4.   Application.RemoveAsyncCalls(Self);
  5.   ...
  6. end;

The same thing may happen with the second parameter of the AsyncCall() when used to transfer a pointer/reference to some dynamic object. It may be already destroyed at the time the handler is executed.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

ssawgift

  • New Member
  • *
  • Posts: 48
    • My Personal Website
Re: How interact with UI controls in a thread (started with BeginThread)?
« Reply #14 on: December 03, 2022, 11:41:58 am »
I have some advanced experience with multithreading. I believe this is not a specific problem to a single language or framework. It's a problem of multithreading design by nature. Everyone has to be very careful with async processing and bear in mind everytihng should be well guarded if it should be accessed by more than one thread.

I don't know how it works behind the scenes of RemoveAsyncCalls, I'd rather not using it because it may abruptly kill any ongoing async work which might leave some conditions corrupt (e.g., event signal'ed but not unset). In the most simple and naive case, I prefer setting up a boolean variable to indicate if it is valid to perform any work in an async procedure.

Well, since the Application.AsyncCall() gets a TDataEvent parameter, which is 'of object', usually it contains an internal reference to a TForm. That handler (the parameter) will be invoked in the future, the form may be non-existent, i.e. the internal reference will be dangling.

To prevent this, it isn't enough just to stop firing AsyncCall's towards that form, you should also remove the already pending:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormDestroy(Sender: TObject);
  2. begin
  3.   // Remove pending async calls
  4.   Application.RemoveAsyncCalls(Self);
  5.   ...
  6. end;

The same thing may happen with the second parameter of the AsyncCall() when used to transfer a pointer/reference to some dynamic object. It may be already destroyed at the time the handler is executed.
« Last Edit: December 03, 2022, 11:46:17 am by ssawgift »

 

TinyPortal © 2005-2018