[delphi] les threads

[delphi] les threads

Ils sont fondamentaux dans Windows. Alors pourquoi se priver de les utiliser ?

Description

Un thread ... :

  • est un processus qui se répète indéfiniment tant qu'il n'a pas été arrêté,
  • a par défaut le même flux que l'application qui l'utilise,
  • est géré entièrement par Windows,
  • possède une priorité (propriété Priority), un handle (propriété Handle) et un identifiant (propriété ThreadID)

Mais surtout, une application qui utilise deux threads peut faire deux choses à la fois. Ce qui n'est généralement pas possible lors que vous développez une application sans gérer de threads (car il n'y a alors qu'un seul thread). Au passage, un processeur ne sait faire qu'une chose à la fois : Windows dispatche le travail, ce qui donne l'illusion d'un travail en simultané. C'est là que la priorité joue son rôle. Quel thread doit être exécuté prioritairement par rapport à un autre ? Par exemple, le gestionnaire de souris est plus important que votre application, et c'est normal. Votre application aura donc une priorité inférieure.

Une haute priorité assure également une meilleure régularité de vitesse d'exécution dans le cadre d'un traitement long.

Mais si un thread effectue des opérations périodiquement, il ne peut en aucun cas être assimilé à un TTimer, en raison de ses caractéristiques, de son fonctionnement et surtout de la régularité sans faille des threads (les TTimer sont peu précis et pas toujours exécutés au bon moment, voire pas du tout si l'application est en plein travail). J'ajouterai qu'avec un thread, vous pouvez chronométrer des temps même si un gros jeu en 3D est lancé, ce que n'aurait pas permis une méthode avec un TTimer.

Conclusion : avec un thread, le traitement des données est assuré de fonctionner toujours quels que soient les processus logés en mémoire. N'hésitez pas à regarder les exemples fournis avec Delphi. Ça peut donner des idées...

Créer et utiliser un thread

J'espère que vous êtes bons sur le développement des classes, car sinon il va falloir relire le tutoriel N°191.

Voici un thread fonctionnel dont le rôle est de ne rien faire... Oh ?

Créons une nouvelle unité que l'on va nommer "MonThread.pas". Copiez ce qui suit :

unit MonThread;
interface
uses Windows, Classes, SysUtils;

type
  TPersoThread = class(TThread)
  private
    procedure CentralControl;
  public
    constructor Create(CreateSuspended:boolean);
     destructor Destroy; override;
  protected
    procedure Execute; override;
  end;

implementation

constructor TPersoThread.Create(CreateSuspended:boolean);
begin
  inherited Create(CreateSuspended);
  FreeOnTerminate:=false;
  Priority:=tpNormal;
end;

destructor TPersoThread.Destroy;
begin
  //déchargez la mémoire ici si vous avez créé des objets
  inherited;
end;

procedure TPersoThread.CentralControl;
begin
  //écrivez ici ce que doit faire votre thread à un instant T donné
end;

procedure TPersoThread.Execute;
begin
  repeat
    Sleep(500); //en millisecondes
    Synchronize(CentralControl);
  until Terminated;
end;
end.

Pour utiliser ce thread dans votre nouvelle application, il suffit de rajouter dans "Unit1.pas" tout ce qui est signalé en commentaires :

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics,
  Controls, Forms, Dialogs, MonThread; // Ajouter : , MonThread;

type
  TForm1 = class(TForm)
  private
    { Déclarations privées }
  public
    { Déclarations publiques }
  end;

var Form1 : TForm1;
    MThrd : TMonThread;  // ligne à ajouter

implementation
{$R *.DFM}

initialization   // ligne à ajouter
  MThrd:=TMonThread.Create(true);  // ligne à ajouter
finalization  // ligne à ajouter
  MThrd.Free;  // ligne à ajouter
end.

Dans le cas ci-dessus, pour lancer le thread, il suffira de faire MThrd.Resume;
Pour suspendre le thread, il suffira de faire MThrd.Suspend;

C'est tout simple. Il faut que vous en soyez convaincu ;)

Décortiquons nos écrits

"MonThread.pas"

Reprenons notre fichier "MonThread.pas". Qu'y a-t-il dedans ?

Il y a déjà un héritage depuis TThread. Pour information :

TThread = class;

.

Le constructor public :

  • Il configure la propriété de TThread nommée FreeOnTerminate. Mieux vaut la laisser toujours sur false en raison de la structure en INITIALIZATION...FINALIZATION de "Unit1.pas". Comme son nom l'indique, le Thread subit .Free si vous appelez .Terminate alors que FreeOnTerminate vaut true. Donc, on met false pour éviter les conflits.
  • Il y a aussi configuration de la priorité du thread. Les différentes possibilités de la plus faible à la plus forte sont les suivantes. On rappelle que haute rapidité ne rime pas avec haute vitesse d'exécution. C'est juste que si le système est occupé par certains processus, alors un tpTimeCritical permet de réduire un peu les pertes de temps.

    tpLowest
    tpLower
    tpNormal => valeur par défaut
    tpHigher
    tpHighest
    tpTimeCritical => priorité absolue

    tpIdle => dans les temps morts de l'application
    • C'est dans ce bloc qu'on initialise les variables et les objets.
    • Vous pouvez édulcorer le constructor de paramètres additionnels, tout en gardant le inherited.

    Le destructor public permet de libérer les objets qui auraient été créés dans le constructor : TStringList, TPanel... par exemple. S'il ne reste que inherited dans le destructor, vous pouvez faire disparaître le destructor (superflu).

    Dans le thread, il nous faut une procédure privée qui effectuera une tâche périodiquement. Par exemple : tester l'existence d'un fichier et agir en conséquence, vérifier un paramètre système... etc.

    Et enfin, il nous faut le corps crucial du thread : la procédure Execute. Il faut garder la structure qui est donnée ci-dessus. Le Sleep permet de suspendre temporairement le thread sans modifier ses propriétés internes. En fait, le Sleep est indispensable pour ne pas bloquer et figer tout le système d'exploitation. Ensuite, on appelle la procédure Synchronize dont on met en paramètre le nom de la procédure qui doit être exécutée. Vous allez me dire : pourquoi ne pas appeler directement la procédure passée en paramètre ? En fait, ça permet d'éviter les conflits du multi-threading (quand il y a plusieurs threads qui s'exécutent en parallèle). Si vous voulez paramétrer CentralControl, vous n'avez pas d'autres choix que de développer des propriétés. Enfin, on boucle Sleep et Synchronize tant que le thread est actif.

    "Unit1.pas"

    Passons maintenant au fichier "Unit1.pas"

    Ici, on utilise qu'un seul thread. On déclare l'unité-squelette, la variable thread qui sera véritablement notre thread (pour l'instant, on n'avait développé que son squelette) et enfin on crée les blocs qui vont initialiser et détruire le thread respectivement quand l'application se lance et se ferme (merci INITIALIZATION...FINALIZATION).

    Insistons sur le fait qu'un TMonThread.Create(true) crée un thread initialement arrêté. Pour le lancer, on utilise la procédure Resume. De même, TMonThread.Create(false) crée un thread initialement actif et qui doit subir la procédure Suspend pour l'arrêter sans le détruire.

    Tanguy ALTERT,
    http://altert.family.free.fr/

Ce document intitulé « [delphi] les threads » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.
Rejoignez-nous