Sunday, August 25, 2013

Threads with Lazarus

It seems every time I use threads in a program I make, I need to lookup again how threads work. So if you're like me perhaps this will help you.

Here's a more complete tutorial to read.

Threads are a great way to do a large amount of processing in your program while allowing the visual part of your program to remain responsive. If you can split up the processing into several threads you can take advantage of modern processors multi core capability and complete a job in a fraction of the time.

Lazarus programs can use threads in GUI and Console programs. If you use threading in your program you should define the compiler option -dUseCThreads. This can be done in Project->Options->Compiler Options->Other - Custom Options. This does nothing under Windows but on any *nix it includes a needed threadmanager. If your program crashes with the first use of thread.Start; then this is probably your problem.

Now that your program is thread enabled here's some basic information about threads. You must subclass the TThread type and make your own thread type and override the Execute method.

The Execute method is never called directly in your program. It is the method the thread will run on it's own when it is started. If you are using MyThread.Execute then you are making a mistake.

Here's a basic example of a new thread type

type 
  TFooThread = class(TThread) 
  protected
    procedure Execute; override;
  end;

... 

implementation 

procedure TFooThread.Execute;
begin
  // do stuff 
end;

 
To create an instance of your thread you can use

FooThread := TFooThread.Create(True);

The argument True creates the thread in suspended state, meaning the OS thread is created but not yet run. If you use False then the thread begins and Execute is called immediately.  You can of course make your own constructor with whatever parameters you need and use inherited Create(False) there.

To start a thread you should use FooThread.Start. FooThread.Resume is deprecated as well as .Suspend which is not reliable on *nix's and can cause your program to hang.

Now your thread is running and working and making your life easier. Your program is snappier and you decide that you need to make your program display the progress your thread is making processing some data.

So in TFooThread.Execute you add Form1.ProgressBar1.Position := SomeValue.
Bad idea. It may work or your program could immediately crash or it might cause some other harder to find error that happens later. Never ever do this.

The reason this is so bad is because the GUI part of your program is run in a separate thread (duh!) and has it's own memory area. Changing the memory in one thread from another needs to be coordinated carefully. You should never change a LCL control value from a thread other than the main process.

A way to update the main thread from another thread is the Synchronize(@SomeProc) method. The Syncronize method, which is called within a created thread, causes the thread to pause and wait for the main thread to call a procedure. Also see CritialSections from the link at the beginning of this article

When your thread terminates you should assume that any code in it's destructor may be called a thread other than your main process thread, especially if you set FreeOnTerminate to True. If you assign a handler for the OnTerminate property then that procedure will be run in the main thread.

After all the code in the Execute method is run your thread cannot be restarted. You will have to free and create another instance of the thread type you need. It's common to include while not Terminated do begin ... end around the code inside the Execute method in order to use a thread multiple times without having to recreate it.

Most of the basic stuff I have run into is now covered. Go code, or read some more: Threading Tutorial on the Wiki

1 comment:

usbrescate said...

Por si a alguien le sirve, yo utilize este código mediante "hilos" en lazarus para descargar un programa, pues sin hilo en la descarga congelaba la aplicación hasta que acabáse:

--------------------------------------------------
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, httpsend;

type
{ TFooThread }
TFooThread = class(TThread)
protected
procedure Execute; override;
end;

{ TForm1 }

TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

function DownloadHTTP(URL, TargetFile: string): Boolean;
var
HTTPGetResult: Boolean;
HTTPSender: THTTPSend;
begin
Result := False;
HTTPSender := THTTPSend.Create;
try
HTTPGetResult := HTTPSender.HTTPMethod('GET', URL);
if (HTTPSender.ResultCode >= 100) and (HTTPSender.ResultCode<=299) then begin
HTTPSender.Document.SaveToFile(TargetFile);
Result := True;
end;
finally
HTTPSender.Free;
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin

end;

procedure TForm1.FormCreate(Sender: TObject);
var
foothread: TFooThread;
begin
FooThread := TFooThread.Create(True);
// antes de que comienze el hilo aqui poner mi codigos de siempre

foothread.Start;
end;


procedure TFooThread.Execute;
begin
//while (not Terminated) and (true {any condition required}) do begin
DownloadHTTP('http://casa/usb_rescate_plus.zip','usb_rescate_plus.zip');
// do stuff
end;

end.


De tantos intentos al menos logré algo, espero les sirva, permiso.