1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
| {
Demo Multithread
Application réalisée dans le but de montrer comment réaliser
du multithread avec Delphi (version XE2 dans ce cas précis).
Ici, le multithread servira à "découper" une longue procédure (boucle)
en multiple opérations simultanées.
Un ListBox contient plusieurs URLs, et nous souhaitons savoir laquelle
de ces URLs contient une expression précise.
L'opération se présente ainsi : on prend une URL ; on récupère son contenu
par GET via Indy que l'on affecte à une StringList ; on vérifie
si l'expression choisie est présente dans la StringList ;
et ensuite nous attribuons un résultat positif ou négatif à l'opération.
Le multithread est encore tout frais pour moi, ce code est probablement
optimisable et/ou comporte quelques erreurs :)
Les commentaires sont à lire dans l'ordre d'exécution du programme.
Chaque commentaire concerne le code du dessous.
Beny (Delphi forum, Developpez.com)
}
unit Main;
interface
uses
// Pour synchroniser les threads, vous aurrez besoin de "System.SyncObjs".
// Pour utiliser Indy (GET), vous aurez besoin de "IdHTTP".
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls, Vcl.Samples.Spin, System.SyncObjs, IdHTTP;
type
TFormMain = class(TForm)
ListBox: TListBox;
LabelThreads: TLabel;
SpinEditThreads: TSpinEdit;
ButtonStart: TButton;
ButtonStop: TButton;
LabelTitre: TLabel;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
procedure ButtonStartClick(Sender: TObject);
procedure ButtonStopClick(Sender: TObject);
private
public
end;
// La CLASS de notre thread.
TDemoThread = class(TThread)
// Dans PRIVATE nous plaçons les variables qui seront modifiées
// par les différents threads simultanément, et qui pourront communiquer
// entre le thread et les synchronisations sans interférences.
private
URL: String;
LigneActive, Resultat: Integer;
protected
// La procédure principal.
procedure Execute; override;
public
// Pour la synchronisation de fin de procédure.
procedure Finish;
// Pour la synchronisation de début de procédure.
procedure Initialize;
// Ce qui lancera chaque thread.
constructor Create(CreateSuspended: Boolean);
end;
var
FormMain: TFormMain;
CS: TCriticalSection; // La CriticalSection (voir FormCreate plus bas).
SL: TStringList; // La StringList (voir FormCreate plus bas).
Threads, Ligne: Integer; // Nous y reviendrons plus tard...
Work: Boolean; // Quand l'opération commence, WORK passe à TRUE.
implementation
{$R *.dfm}
// Le thread est crée ici.
constructor TDemoThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
end;
// Voici le code principal du thread.
// Vous verrez qu'il n'interagit jamais avec l'interface, cette étape sera
// gérée par les synchronisations que nous verrons plus bas.
procedure TDemoThread.Execute;
var
Raw: TStringList; // StringList temporaire pour stocker le code-source.
HTTP: TIdHTTP; // On déclare ici le composant HTTP de Indy.
begin
// Si "WORK=TRUE" l'opération continue normalement...
while Work do
begin
// Ici nous allons vérifier s'il reste des URLs à traiter.
// Nous allons utiliser ici la CriticalSection afin que cette opération
// ne soit pas effectuée simultanément par plusieurs threads ;
// ce qui fausserait le résultat.
// Nous entrons en section critique...
CS.Enter;
// Nous incrémentons de "+1" la position actuelle dans la StringList
// contenant les URLs à traiter.
// Pour rappel, LIGNE est un Integer.
Inc(Ligne);
// Si la position actuelle est inférieur à la dernière ligne
// de la StringList, alors nous allons pouvoir traiter cette URL ;
if Ligne < SL.Count then
LigneActive := Ligne
else
// Sinon, on en déduit qu'il n'y a plus d'URLs à traiter.
Work := False;
// Nous quittons la section critique.
CS.Leave;
// Si la vérification effectuée ci-dessus donne un résultat positif
// alors nous pouvons procéder à l'opération.
if Work then
begin
// On récupère l'URL en cours et on l'affecte à un simple String.
// Ce n'est pas essentiel mais cela limite les connexions entre
// la StringList contenant les URLs et les threads.
// Important : comme cette variable sera utilisée dans le
// thread principal (ici) et dans les SYNCHRONIZE, il est nécessaire
// de déclarer "URL:STRING" dans PRIVATE de la CLASS "TDemoThread".
URL := SL[LigneActive];
// On intéragit avec l'interface afin que l'utilisateur sache
// ce qu'il se passe et où nous en sommes.
Synchronize(Initialize);
// On crée la StringList temporaire.
Raw := TStringList.Create;
// On crée le composant HTTP et on le paramètre vite-fait, bien-fait.
HTTP := TIdHTTP.Create;
HTTP.HandleRedirects := True;
HTTP.ReadTimeout := 15000;
HTTP.ConnectTimeout := 15000;
// Dans un TRY/EXCEPT on fait un GET pour récupérer le code source
// de l'URL, que l'on va affecter à la StringList créée précédemment.
try
Raw.Text := HTTP.Get(URL);
except
// En cas d'erreur, pas de panique (EXCEPT peut rester vide).
Application.ProcessMessages;
end;
// On vérifie si RAW.TEXT (le code-source) contient l'expression "utf-8".
// Ici nous utilisons POS mais nous pouvions utiliser ANSICONTAINSSTR.
if Pos('utf-8', Raw.Text) <> 0 then
Resultat := 1 // La réponse est oui !
else
Resultat := 0; // La réponse est non !
// On libère les composants utilisés via FREE ou FREEANDNIL.
Raw.Free;
HTTP.Free;
// On synchronise la réponse avec la procédure FINISH,
// qui pourra intéragir alors avec l'interface.
Synchronize(Finish);
end;
end;
// Le thread est terminé donc on retire (décrémente) "-1" à la StringList.
// Pour rappel, THREADS est un Integer.
Dec(Threads);
// Plus de threads dans la liste, on en déduit que l'opération est terminée !
if Threads <= 0 then
ShowMessage('Traitement complet terminé !');
end;
// Nous modifions la ligne de l'URL en cours lorsque l'opératon commence.
procedure TDemoThread.Initialize;
begin
// Intéraction avec l'interface possible,
// car cette procédure est appelée via SYNCHRONIZE.
FormMain.ListBox.Items.Strings[LigneActive] := URL + ' - En cours...';
end;
// L'opération est terminée, place à l'affectation des résultats.
procedure TDemoThread.Finish;
begin
case Resultat of
1: // Résultat positif, donc l'URL contient l'expression !
begin
// Intéraction avec l'interface car SYNCHRONIZE :)
FormMain.ListBox.Items.Strings[LigneActive] :=
URL + ' - Contient l''expression "utf-8"';
end;
0: // Résultat négatif, donc l'URL ne contient pas l'expression !
begin
// Intéraction avec l'interface car SYNCHRONIZE :)
FormMain.ListBox.Items.Strings[LigneActive] :=
URL + ' - Ne contient pas l''expression "utf-8"';
end;
end;
end;
{ ============================================================================ }
procedure TFormMain.ButtonStartClick(Sender: TObject);
var
i: Integer;
begin
// On vérifie que le ListBox contient bien des éléments,
// sinon ça risque de ne pas fonctionner...
// Bien entendu, cette vérification pourrait être plus poussée.
if FormMain.ListBox.Items.Count > 0 then
begin
// Nous passons WORK à TRUE afin d'indiquer au programme
// que l'opération est lancée !
Work := True;
// Si le nombre de threads souhaités est supérieur au nombre d'URLs
// à traiter, alors on baisse le nombre de threads automatiquement.
if SpinEditThreads.Value > ListBox.Items.Count then
SpinEditThreads.Value := ListBox.Items.Count;
// Afin de ne pas utiliser le ListBox (donc l'interface),
// nous utiliserons une StringList
SL.Assign(ListBox.Items);
// LIGNE (Integer) sera la position actuelle dans la StringList (SL).
// Nous devons l'initialiser à "-1" car le début du thread
// va l'incrémenter de "+1" (INC) ; LIGNE se doit donc de partir de 0.
Ligne := -1;
// Nous indiquons le nombre de threads choisi par l'utilisateur.
Threads := SpinEditThreads.Value;
// Voici enfin la création de chaque thread !
// Un SPINEDIT est utilisé afin de définir le nombre de threads maximum.
for i := 1 to SpinEditThreads.Value do
TDemoThread.Create(False);
end
else
// ListBox vide, donc on engueule l'utilisateur, c'est normal :D
ShowMessage('Je te laisse deviner l''erreur...');
end;
procedure TFormMain.ButtonStopClick(Sender: TObject);
begin
// Si "WORK=TRUE" on en déduit que l'opération est déjà en cours...
if Work then
begin
// Sinon, on passe WORK à FALSE afin qu'auncun nouveau thread ne soit créé.
Work := False;
// Puis un message indiquant à l'utilisateur que l'opération a été stoppée.
ShowMessage('Stoppé ! Attendons la fin des derniers threads actifs...');
end
else
// ...donc voici un beau message pour en informer l'utilisateur.
// Il va de soit qu'il est plus élégant de désactiver les controls
// inutiles lorsque l'opération est en cours.
// Mais faisons très simple ici :)
ShowMessage('Rien à stopper, l''opération n''a pas commencée !');
end;
procedure TFormMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// On libère la StringList avant de fermer.
SL.Free;
// On libère la CriticalSection avant de fermer.
CS.Free;
end;
procedure TFormMain.FormCreate(Sender: TObject);
begin
// On crée la StringList qui servira à stocker de manière virtuelle
// le contenu du ListBox.
SL := TStringList.Create;
// On crée la CriticalSection qui servira à indiquer l'action
// qui devra être réalisée par les threads un par un,
// et non simultanément (pour éviter les conflits).
CS := TCriticalSection.Create;
end;
end. |
Partager