Recent

Author Topic: Multithread example that can access GUI  (Read 14089 times)

Trenatos

  • Hero Member
  • *****
  • Posts: 535
    • MarcusFernstrom.com
Multithread example that can access GUI
« on: August 11, 2015, 04:23:05 pm »
I'm kicking off process and it's working great, the problem is that it of course locks up the GUI.

I'm trying to figure out how to do multi-threading so that 1. running processes won't lock up GUI, and 2. so I can run multiple processes.

I found examples online, but haven't been able to get them working.

Can someone point me to a decent source with example, or write up a small example of multi-thread that interacts with the GUI?

HeavyUser

  • Sr. Member
  • ****
  • Posts: 397
Re: Multithread example that can access GUI
« Reply #1 on: August 11, 2015, 04:31:20 pm »
I'm kicking off process and it's working great, the problem is that it of course locks up the GUI.

I'm trying to figure out how to do multi-threading so that 1. running processes won't lock up GUI, and 2. so I can run multiple processes.

I found examples online, but haven't been able to get them working.
What did you try and why you can't use it?
Can someone point me to a decent source with example, or write up a small example of multi-thread that interacts with the GUI?
No! Single thread can access the GUI and that is the main thread only. Forget about refreshing the gui from threads it will never going to work. Decouple the data from the gui make the data structures thread safe inform the gui for changes. Thats it the gui will refresh it self when it has time as long as it knows that the data have changed.

eny

  • Hero Member
  • *****
  • Posts: 1634
Re: Multithread example that can access GUI
« Reply #2 on: August 11, 2015, 04:49:13 pm »
Since you have read this: http://wiki.lazarus.freepascal.org/Multithreaded_Application_Tutorial,
what questions do you still have?
All posts based on: Win10 (Win64); Lazarus 2.0.10 'stable' (x64) unless specified otherwise...

Trenatos

  • Hero Member
  • *****
  • Posts: 535
    • MarcusFernstrom.com
Re: Multithread example that can access GUI
« Reply #3 on: August 11, 2015, 05:23:38 pm »
Plenty of questions but I'm starting simple.

I got this working:  http://forum.codecall.net/topic/70827-very-simple-multithreading/

I'm trying to modify it to update a label.

I've added a global variable, gTextString

I'm updating the label in the start button.
StartBeeping;
  while true do
  begin
    theLabel.caption := gTextString;
    Application.ProcessMessages;
    if uStopBeeping then Exit;
  end;

If I set the variable to some text in the thread, it works, but if I do TimeToStr(Time) in the thread, I get a SigSegv, but not if I do it in the button handler.

kapibara

  • Hero Member
  • *****
  • Posts: 610
Re: Multithread example that can access GUI
« Reply #4 on: August 11, 2015, 05:24:40 pm »
Synchronize and Queue are routines for updating the GUI from threads. Check this discussion for a demo that might be useful:

http://forum.lazarus.freepascal.org/index.php/topic,26992.msg166744.html#msg166744

Callbacks, methodpointers, dispatchmethods and event properties are useful terms when doing GUI <-> thread communiction. They are all part of the example above and its easier to actually do it than to describe. If I remember right, these are the steps in the demo:

  • First create a method in the form unit that updates the form when called.
  • Define a method type for the thread. (procedure of object)
  • Declare a method pointer variable of this type in your derived thread class
  • Create a Dispatch method in the derived thread class
  • Create an event property in the derived thread class
  • Assign the main form callback method to the threads event property
  • For example in the threads execute method, call synchronize on the dispatch method and your GUI will be updated.
« Last Edit: August 11, 2015, 05:32:10 pm by kapibara »
Lazarus trunk / fpc 3.2.2 / Kubuntu 22.04 - 64 bit

derek.john.evans

  • Guest
Re: Multithread example that can access GUI
« Reply #5 on: August 11, 2015, 06:06:40 pm »
Thread coding is somewhat tricky. Every time I code some, I seem to use a slightly different scheme.

In the past Ive hard connected threads to GUI with pointers to controls. This meant, a GUI element couldn't deleted itself, until after the thread was terminated. Which, meant, the GUI elements needed pointers to threads, which meant threads couldn't freeonterminate, so I ended up implementing all these callbacks, bla bla bla.

All because I used pointers to GUI elements. So, now I don't. I send the thread the component name, which it then has to find. If it doesn't, it terminates and free's. Which means, the GUI doesn't need to know about the threads. You can just free a GUI control without issue.

With that idea, I coded this example:
Code: Pascal  [Select][+][-]
  1. type
  2.  
  3.   TProcessThread = class(TThread)
  4.   strict private
  5.     FExecutable, FParameters, FLine, FSynEditName: String;
  6.   protected
  7.     procedure Execute; override;
  8.     procedure SynchronizeWriteLine;
  9.   public
  10.     constructor Create(const AExecuteable, AParameters, ASynEditName: String);
  11.   end;
  12.  
  13. constructor TProcessThread.Create(const AExecuteable, AParameters, ASynEditName: String);
  14. begin
  15.   FExecutable := AExecuteable;
  16.   FParameters := AParameters;
  17.   FSynEditName := ASynEditName;
  18.   FreeOnTerminate := True;
  19.   inherited Create(False);
  20. end;
  21.  
  22. procedure TProcessThread.SynchronizeWriteLine;
  23. var
  24.   LLastLine: String;
  25.   LSynEdit: TSynEdit;
  26. begin
  27.   if not Terminated then
  28.   begin
  29.     try
  30.       LSynEdit := Application.MainForm.FindComponent(FSynEditName) as TSynEdit;
  31.       if not Assigned(LSynEdit) then
  32.       begin
  33.         Terminate;
  34.       end else begin
  35.         with LSynEdit.Lines do
  36.         begin
  37.           BeginUpdate;
  38.           try
  39.             if Count = 0 then
  40.             begin
  41.               LLastLine := EmptyStr;
  42.             end else begin
  43.               LLastLine := Strings[Count - 1];
  44.               Delete(Count - 1);
  45.             end;
  46.             AddText(LLastLine + FLine + LineEnding);
  47.             while Count > 500 do
  48.             begin
  49.               Delete(0);
  50.             end;
  51.           finally
  52.             EndUpdate;
  53.           end;
  54.         end;
  55.         LSynEdit.CaretY := MaxInt;
  56.         Application.ProcessMessages;
  57.       end;
  58.     except
  59.       Terminate;
  60.     end;
  61.   end;
  62. end;
  63.  
  64. procedure TProcessThread.Execute;
  65.  
  66.   function LStreamReadChunk(const AStream: TStream): String;
  67.   var
  68.     LBuffer: array[byte] of Char;
  69.   begin
  70.     LBuffer[0] := #0;
  71.     SetString(Result, LBuffer, AStream.Read(LBuffer, SizeOf(LBuffer)));
  72.   end;
  73.  
  74. var
  75.   LProcess: TProcess;
  76. begin
  77.   try
  78.     LProcess := TProcess.Create(nil);
  79.     try
  80.       LProcess.Options := LProcess.Options + [poUsePipes, poStderrToOutPut];
  81.       LProcess.ShowWindow := swoHIDE;
  82.       LProcess.Executable := FExecutable;
  83.       LProcess.Parameters.Text := FParameters;
  84.       LProcess.Execute;
  85.       while not Terminated and LProcess.Running do
  86.       begin
  87.         while not Terminated and LProcess.Running and (LProcess.Output.NumBytesAvailable = 0) do
  88.         begin
  89.           Sleep(400);
  90.         end;
  91.         FLine := LStreamReadChunk(LProcess.Output);
  92.         Synchronize(@SynchronizeWriteLine);
  93.       end;
  94.       repeat
  95.         FLine := LStreamReadChunk(LProcess.Output);
  96.         Synchronize(@SynchronizeWriteLine);
  97.       until Terminated or (LProcess.Output.NumBytesAvailable = 0);
  98.     finally
  99.       FreeAndNil(LProcess);
  100.     end;
  101.   except
  102.     on E: Exception do
  103.     begin
  104.       FLine := E.Message + LineEnding;
  105.       Synchronize(@SynchronizeWriteLine);
  106.     end;
  107.   end;
  108.   FLine := '**** PROCESS COMPLETE ****' + LineEnding;
  109.   Synchronize(@SynchronizeWriteLine);
  110. end;  
  111.  

With two helper functions:
Code: Pascal  [Select][+][-]
  1. procedure RunExecutable(const AExecutable, AParameters: String; const ASynEdit: TSynEdit);
  2. begin
  3.   TProcessThread.Create(AExecutable, AParameters, ASynEdit.Name);
  4. end;
  5.  
  6. procedure RunDosCommand(const ACommand: String; const ASynEdit: TSynEdit);
  7. begin
  8.   RunExecutable('cmd', '/C'#13 + ACommand, ASynEdit);
  9. end;
  10.  

With some test examples:
Code: Pascal  [Select][+][-]
  1.   RunDosCommand('dir /s C:\WINDOWS', SynEdit1);
  2.   RunExecutable('java', '-help', SynEdit2);
  3.   RunDosCommand('dir /s C:\', SynEdit3);    
  4.  

NOTE: The thread looks for the component name in the main form, so this only works for TSynEdit controls that are on the main form.
« Last Edit: October 02, 2015, 03:27:01 am by Geepster »

Trenatos

  • Hero Member
  • *****
  • Posts: 535
    • MarcusFernstrom.com
Re: Multithread example that can access GUI
« Reply #6 on: August 11, 2015, 07:49:05 pm »
derek, that is a beautiful example, it works perfectly!

I'll dig into it and see if I can adapt it to do what I need the final project to do

derek.john.evans

  • Guest
Re: Multithread example that can access GUI
« Reply #7 on: August 11, 2015, 08:13:26 pm »
derek, that is a beautiful example, it works perfectly!

I'll dig into it and see if I can adapt it to do what I need the final project to do

Cool. As a tip, when you go to upscale that idea, you will most likely use a TPageControl. Therefore, each TSynEdit will be dynamically created inside TTabSheets. The TPageControl will own the TSynEdit's, so the FindComponent command needs to be changed to:
Code: [Select]
FormMain.PageControl.FindComponent(FSynEditName) as TSynEdit; 

Each TSynEdit needs to have a new name, so, use a counter to create a new index for a name.

EG:
Code: [Select]
procedure TFormMain.Button1Click(Sender: TObject);
var
  LSynEdit: TSynEdit;
  LTabSheet: TTabSheet;
begin
  LTabSheet := PageControl.AddTabSheet;
  LTabSheet.Caption := 'Console';
  LSynEdit := TSynEdit.Create(PageControl);
  LSynEdit.Name := 'SynEdit' + IntToStr(FSynEditCounter);
  LSynEdit.Text := EmptyStr;
  LSynEdit.Align := alClient;
  LSynEdit.Parent := LTabSheet;
  LTabSheet.Show;
  Inc(FSynEditCounter);
  RunDosCommand('dir /s C:\', LSynEdit);
end;   

Then if you have a 'stop process' button it just has todo this:
Code: [Select]
procedure TFormMain.Button2Click(Sender: TObject);
begin
  PageControl.ActivePage.Free;
end; 

And all is well in the world.

Trenatos

  • Hero Member
  • *****
  • Posts: 535
    • MarcusFernstrom.com
Re: Multithread example that can access GUI
« Reply #8 on: August 11, 2015, 08:26:03 pm »
I'm thinking of just using one synedit and a tab control, letting each process save to a variable somewhere, and update the synedit to display whatever process' tab is clicked on.

That way I only need a single synedit, and all updates happen through the onclick on the tabcontrol.

Not sure if that's easier or not, but it's a solution I've used before

derek.john.evans

  • Guest
Re: Multithread example that can access GUI
« Reply #9 on: August 11, 2015, 08:35:18 pm »
Maybe. The code I've written relies on the TStrings.AddText() which handles the the various CRLF/LFCR/LF/CR possibilities.

Which means, you would still need a bunch of TStringList's. Which arn't components, so they cant be searched for via FindComponent(). So, by the time you write a TObjectList for the TStringList's, and copy the TStringList's into the one TSynEdit when clicking a tab,

you might as well have multiple TSynEdit's  ;D

Trenatos

  • Hero Member
  • *****
  • Posts: 535
    • MarcusFernstrom.com
Re: Multithread example that can access GUI
« Reply #10 on: August 11, 2015, 11:14:04 pm »
I'm racing now, getting lots done in a short time.

The problem I'm having right now is simple, I don't understand threads.

As I kick off a thread, I need to save a reference to it so that I can later kill it through a button.

I looked at your added code, but don't understand how the referencing would work.

derek.john.evans

  • Guest
Re: Multithread example that can access GUI
« Reply #11 on: August 11, 2015, 11:48:13 pm »
First up, you cant kill a thread. You can politely ask it to terminate, but then it is upto the thread whether it will or not. Hence, why in the TThread.Execute you check Terminated, to see if it should continue or not.

Problem is, as soon as you think about the idea of calling TThread.Terminate, you can no longer use FreeOnTerminate, since, it is possible that the pointer to the thread is no longer valid.

Unless, the thread has a callback system for when it terminates, or it has a pointer to the pointer of itself, which it can set to nil, which is a little dodgy if you ask me. It all gets very messy very quickly.

So, how my code works is, the thread syncronizes with the main thread, and trys to find the GUI control it uses for display stuff. in this case, a TSynEdit control. If it isn't found, then the thread terminates itself.

So, all you have todo is free the TSynEdit control. The thread will terminate when its ready todo so.

Simple.

**EDIT* You may want to terminate the thread without freeing the TSynEdit. So, use a flag in TSynEdit like Enabled.

Set TSynEdit.Enabled := False. The thread can detect that and terminate itself, but, before it does, it should set TSynEdit.Enabled to True. (in the syncronized method)



« Last Edit: August 11, 2015, 11:51:15 pm by derek.john.evans »

Trenatos

  • Hero Member
  • *****
  • Posts: 535
    • MarcusFernstrom.com
Re: Multithread example that can access GUI
« Reply #12 on: August 11, 2015, 11:54:37 pm »
Aha, I see.

That makes it a little more complicated, as it seems Java processes do not like to exit nicely.

The simplest way I can think of is to send the ctrl + c combination, that will kill the process through its built-in functionality.

Without doing that, the process just stays up and never terminates.

derek.john.evans

  • Guest
Re: Multithread example that can access GUI
« Reply #13 on: August 11, 2015, 11:57:15 pm »
Try putting this line in:
Code: [Select]
     LProcess.Terminate(-1); // NEW LINE: Unsure if the code will make a difference.
     
      // NORMAL CODE FOLLOWS
      repeat
        FLine := LStreamReadChunk(LProcess.Output);
        Synchronize(@SynchronizeWriteLine);
      until Terminated or (LProcess.Output.NumBytesAvailable = 0); 

rvk

  • Hero Member
  • *****
  • Posts: 6163
Re: Multithread example that can access GUI
« Reply #14 on: August 12, 2015, 12:10:09 am »
First up, you cant kill a thread. You can politely ask it to terminate, but then it is upto the thread whether it will or not.
Uuh, how about KillThread? It calls TerminateThread in Windows.

But you should never use it. Especially in these circumstances. Signaling the thread another way (like you showed) and letting it end on its own is always better than hard-killing the thread. But it is possible.

 

TinyPortal © 2005-2018