Question sur les thread

Résolu
Signaler
Messages postés
166
Date d'inscription
mardi 11 novembre 2003
Statut
Membre
Dernière intervention
13 octobre 2008
-
Messages postés
115
Date d'inscription
jeudi 17 avril 2008
Statut
Membre
Dernière intervention
16 juillet 2008
-
Bonjour a tous,

J'ai une petite interrogation concernant les threads.

Je souhaite arreter mon application a une heure précise. Mon programme fonctionne parfaitement, et actuellement j'utilise un TTimer avec un Interval de 10 secondes qui regarde que l'heure fixée n'est pas arrivée. Si elle est arrivée, l'application se ferme.

Jusqu'ici tous va bien. Le problème c'est que si mon application plante ou reste bloqué dans un traitement (ca ne devrait pas arrivé, mais on ne sais jamais), elle restera bloqué et elle ne se fermera pas a l'heure (puisque l'application etant planté, elle n'ira pas dans la procédure du Timer).

En gros je voudrais que le thread tourne en boucle en attendant la bonne heure, et que lorsque celle ci arrive, elle ferme l'application ou force la fermeture si l'appli est plantée.

Je me demande donc si je remplace la fonction du Timer par un thread
parallèle, est ce que cela marchera même si le thread principale est
planté? Ou est ce que cette solution ne vous semble pas correcte (je connais pas encore bien les threads et leurs comportements)?

Merci d'avance

19 réponses

Messages postés
1023
Date d'inscription
dimanche 1 août 2004
Statut
Membre
Dernière intervention
17 août 2008
2
Voila ce que ça donne (testé et approuvé par un échantillon de une personne) :

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TAutoTerminateThread = class(TThread)
private
FMainWndHandle: THandle;
FTerminateTime: TDateTime;
FWaitEvent: Cardinal;
protected
procedure Execute; override;
public
constructor Create(MainWindowHandle: THandle; EndTime: TDateTime);
destructor Destroy; override;
{ Obligé de réintroduire cette méthode car on utilise un TEvent,
ce qui n'est malheureusement pas géré nativement par Delphi }
procedure Terminate; reintroduce;
end;

TFrmTest = class(TForm)
BtnQuit: TButton;
BtnPlante: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure BtnQuitClick(Sender: TObject);
procedure BtnPlanteClick(Sender: TObject);
private
FTermThread: TAutoTerminateThread;
end;

var
FrmTest: TFrmTest;

implementation

{$R *.dfm}

constructor TAutoTerminateThread.Create(MainWindowHandle: THandle;
EndTime: TDateTime);
begin
inherited Create(False);
FreeOnTerminate := True;
FMainWndHandle := MainWindowHandle;
FTerminateTime := EndTime;
FWaitEvent := CreateEvent(nil, True, False, nil);
if FWaitEvent = 0 then
RaiseLastOSError;
end;

destructor TAutoTerminateThread.Destroy;
begin
CloseHandle(FWaitEvent);
inherited Destroy;
end;

procedure TAutoTerminateThread.Terminate;
begin
SetEvent(FWaitEvent);
inherited Terminate;
end;

procedure TAutoTerminateThread.Execute;
var
WaitTime: Cardinal;
begin
WaitTime := Trunc(FTerminateTime - Now) * 1000;
if WaitForSingleObject(FWaitEvent, WaitTime) = WAIT_TIMEOUT then
begin
{>> On n'a fait pas appel à Terminate et le temps s'est écoulé: on quitte }

{ D'abord, la méthode "douce" (pas SendMessage car si l'autre thread
est bloqué, celui-ci aussi va se bloquer) }
PostMessage(FMainWndHandle, WM_CLOSE, 0, 0);

{ On attend encore un peu pour voir si l'autre thread fait
appel à Terminate ou s'il est vraiment coincé (5 secondes maxi) }
if WaitForSingleObject(FWaitEvent, 5000) = WAIT_TIMEOUT then
begin
{ Coinçage total: suicide obligé ! }
Beep;
TerminateProcess(GetCurrentProcess, 1);
end;
end;
end;

procedure TFrmTest.FormCreate(Sender: TObject);
begin
{>> Le programme va se fermer 5 secondes après son ouverture }
FTermThread := TAutoTerminateThread.Create(Handle, Now + 5.0);
end;

procedure TFrmTest.FormDestroy(Sender: TObject);
begin
{>> On signale au thread de se terminer: la procédure WaitForSingleObject
renvoit immédiatement WAIT_OBJECT_0 et le thread se détruit normelement }
FTermThread.Terminate;
end;

procedure TFrmTest.BtnQuitClick(Sender: TObject);
begin
Close;
end;

procedure TFrmTest.BtnPlanteClick(Sender: TObject);
begin
while True do
Sleep(10);
end;

end.

Alors, qu'en pensez-vous ?
Messages postés
2527
Date d'inscription
jeudi 15 janvier 2004
Statut
Membre
Dernière intervention
16 octobre 2019
18
Bein, si le panel qui a testé est content, on est contents aussi, nous...  ;)



Pour info, il y a une variante à

WaitForSingleObject. C'est l'utilisation de GetExitCodeProcess qui renvoit le code STILL_ACTIVE tant que le processus est actif... Mais bon! Ca n'apporterait rien dans le cas présent.



Sinon, j'ai pensé à une autre stratégie, sans Thread :

-
Lancer un processus-Timer, uniquement chargé d'attendre l'heure de fermeture. Et, ce serait ce processus qui lancerait ton application avec CreateProcess. A l'heure de fermeture, ce processus-parent mettrait fin à son processus-fils (ton appli).
... Voir si ça apporte un avantage. A votre avis?


PS:
@Flo


« Mais TerminateProcess() libère quand même toutes les ressources »


Oui, mais lorsqu'on met fin à un processus le système met aussi fin à ses Threads. Et ces Threads peuvent manipuler des objets partagés qui risquent de ne pas être libérés correctement, eux...
Messages postés
1023
Date d'inscription
dimanche 1 août 2004
Statut
Membre
Dernière intervention
17 août 2008
2
Je ne vois pas à quoi servirait d'appeler EnumThreadWindows puisque l'envoi d'un message à la fenêtre principale suffit.

@Cari:

Très juste pour le GetExitCodeProcess() mais comme tu le dis, dans tous les cas il faudrait bien attendre une certaine durée avant de tester donc ça revient au même.

Pour TerminateProcess(): lorsqu'un processus est fermé, toute la mémoire utilisateur est libérée (même si les FreeMem, Dispose ou Destroy n'ont pas été appelés) et tous les Handles ouverts sont automatiquement fermés (ceux des fenêtres, des fichiers, des sémaphores, des objets GDI, ...) et la mémoire associée est libérée.
Si tes threads partagent des objets entre eux, ces objets seront détruits comme les autres.
Les seuls soucis qui interviennent sont :
- Aucune procédure de sauvegarde de quelconque donnée n'est appelée.
- Les liaisons réseau sont brutalement coupées (imagine la tête du gars de l'autre côté du fil ^^)
- Si tu as des régions globalement partagées (file mapping par exemple): elles ne sont libérées que lorsque tous les processus qui les utilisent sont fermés. Entre temps, ceux qui restent peuvent lire des données dedans qui risquent d'être corrompes.

Mais bon, quand ton processus est bloqué, je pense qu'il est préférable de le débloquer, coûte que coûte, plutôt que de le laisser consommer ses 100% de ressources CPU et recevoir une facture edf bien gonflée !
Dans ce sens, Sat83 a tout à fait raison de vouloir se prémunir de ce genre de dégâts.

Sinon, comme autre solution: lorsque l'appli est plantée, elle peut demander à l'utilisateur de faire Ctrl+Alt+Suppr, non !? ;-)
Messages postés
1023
Date d'inscription
dimanche 1 août 2004
Statut
Membre
Dernière intervention
17 août 2008
2
Salut !

Si ton thread principal est planté (genre boucle infinie) ou ne vérifie pas périodiquement l'existence de signaux provenant de l'autre thread, ta solution ne fonctionnera pas.

Cette solution que tu proposes est donc strictement identique à l'autre.
Si ton application est déjà multi-threadée, c'est la solution que je te conseille, sinon tu peux largement garder la version "timer" qui est tout à fait acceptable.

Ce que tu peux faire, par contre, c'est changer la propriété Interval du timer en fonction du temps restant avant la fermeture, pour moins consommer en ressources processeur (même si c'est pas énorme une fois toutes les 10 secondes)

Un truc du genre en gros :

var
TempsRestant: Double;
begin
TempsRestant := DateTimeDeFin - Now;
if TempsRestant <= 0 then
Close
else if TempsRestant <= 60 then
Timer.Interval := 5000
else if TempsRestant <= 120 then
Timer.Interval := 40000
else
Timer.Interval := 120*60;
end;

A+
Flo
Messages postés
1023
Date d'inscription
dimanche 1 août 2004
Statut
Membre
Dernière intervention
17 août 2008
2
J'avais pas vu un détail: "forcer la fermeture"

Dans ce cas, alors tu peux utiliser un thread qui appelle la fonction système qui "tue" les processus. Mais je ne sais pas si un processus peut s'auto-tuer, à essayer...
Messages postés
166
Date d'inscription
mardi 11 novembre 2003
Statut
Membre
Dernière intervention
13 octobre 2008

Pour l'instant j'en suis a la solution avec le Timer.

J'envisage de passer a la solution avec un second thread, mais seulement si c'est utile. C'est pour cette raison que j'ai preféré poser la question avant de me lancer dans un developpement inutile qui m'aurait amené au même resultat.

Dans ce que j'imaginais comme solution,  dans mon second thread je voulais soit fermer le programme normalement si c'est possible, soit killer le processus (en passant par un .bat intermediaire).
Messages postés
2527
Date d'inscription
jeudi 15 janvier 2004
Statut
Membre
Dernière intervention
16 octobre 2019
18
Bonsoir,

« Mais je ne sais pas si un processus peut s'auto-tuer »
 
C'est possible.
Dans ce test, le process s'auto-tue au bout de 4 secondes :



type
  TWaitThread = class(TTHread)
  private
    fProcessHndl : Thandle;


// Handle du process.
  protected
    Constructor Create(Hndl : Thandle);
    procedure Execute; override;
  end;






  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
  end;



var
  Form1: TForm1;



implementation

{$R *.dfm}



var  Wait : TWaitThread;



Constructor TWaitThread.Create(Hndl : Thandle);
  begin
  fProcessHndl := Hndl;
  inherited Create(false);
end;




procedure TWaitThread.Execute;
begin
  while not terminated do begin
    sleep(4000); 


//Le process se suicidera au bout de 4s.
    beep; // Dernier soupir.
    TerminateProcess(fProcessHndl, 0);  




//là, le process est décédé à 100%.
  end;
end;







procedure TForm1.Button1Click(Sender: TObject);
  var i : integer;
begin
  Wait := TWaitThread.Create(GetCurrentProcess);


// On lance le Thread.
  i := 1;
  repeat 




//Boucle à la con, mais infinie.
    Edit1.text := inttostr(i);
    Edit1.Refresh;
    inc(i);
  until i = -1;
end;
Messages postés
2527
Date d'inscription
jeudi 15 janvier 2004
Statut
Membre
Dernière intervention
16 octobre 2019
18
... Mais le big problème dans tout ça, c'est que TerminateProcess ne libère aucune ressource...
Messages postés
166
Date d'inscription
mardi 11 novembre 2003
Statut
Membre
Dernière intervention
13 octobre 2008

Merci pour ta solution!

Y'aurait-il un moyen de tenter de fermer le programme "normalement" dans le thread, de detecter si c'est pas possible, et dans ce cas là seulement  forcer la fermeture avec TerminateProcess ?
Messages postés
115
Date d'inscription
jeudi 17 avril 2008
Statut
Membre
Dernière intervention
16 juillet 2008

Si j'été vous j'ivetrais de faire un terminateprocess, fermer comme ça brutement c pas trés propre éssez plus tôt d'envoyé un sms de type WM_CLOSE  ça feras la faire genre SendMessage(fProcessHndl , WM_CLOSE, 0, 0);.

Matrix
Messages postés
1023
Date d'inscription
dimanche 1 août 2004
Statut
Membre
Dernière intervention
17 août 2008
2
Bon alors d'après Cari, le processus peut d'auto tuer
Mais TerminateProcess() libère quand même toutes les ressources (et oui !) ***
Ce qu'il ne fait pas, c'est exécuter les procédures de "finalization", les OnDestroy(), etc...

Donc c'est pas propre si par exemple le programme veut pouvoir sauvegarder son fichier de configuration.

Donc, comme le demande Sat83, on peut ruser :
- Une première demande de fermeture "propre": le sms WM_CLOSE fonctionne parfaitement sur toutes les applications VCL Delphi (ce n'est pas forcément le cas pour les autres langages)
- Si au bout de... disons 5 secondes, le programme ne s'est toujours pas arrêté, on fait appel à TerminateProcess

*** : Quoi que j'ai tout de même un doute pour les fichiers ouverts.
Messages postés
115
Date d'inscription
jeudi 17 avril 2008
Statut
Membre
Dernière intervention
16 juillet 2008

presque parfait , code extras il te manque je pense juste un  callback pour utiliser EnumThreadWindows et pour que ça soit un serialkillerEnumThreadWindows(dwGuiThreadId, EnumProc, 0);
Matrix
Messages postés
2527
Date d'inscription
jeudi 15 janvier 2004
Statut
Membre
Dernière intervention
16 octobre 2019
18
Ultra-chipotage : 



Je ne crois pas que WaitForSingleObject consomme beaucoup en ressources processeur, mais on pourrait peut-être quand même mettre la priorité du Thread à tpLowest : 


FTermThread.Priority := tpLowest;
Messages postés
1023
Date d'inscription
dimanche 1 août 2004
Statut
Membre
Dernière intervention
17 août 2008
2
Ah ouais, niveau chipotage, là tu y es allé sacrément fort.
On peut presque mettre tpIdle alors !

Mais bon, faut pas que le thread s'endorme non plus ! déjà qu'il bosse pas trop...

Ton autre approche est un peu plus lourde et compliquée car il faut créer un autre process, un autre thread...

[[HS]]
C'est compliqué, finalement, de quitter son propre process ! ;-)))
[/HS]
Messages postés
166
Date d'inscription
mardi 11 novembre 2003
Statut
Membre
Dernière intervention
13 octobre 2008

Merci a tous pour vos moultes idées et conseils!

Je testerais tous ça dès que j'aurais un moment, mais en tous cas j'en sais déja beaucoup plus sur le sujet!

Merci.
Messages postés
115
Date d'inscription
jeudi 17 avril 2008
Statut
Membre
Dernière intervention
16 juillet 2008

@[auteur/FLORENTH/316333.aspx florenth]
j'ai juste dit si tu utilise WM_CLOSE  ben!  ça marche pas pour tous les applications, par exemple si tu envoi un sms ou mms de type wm_close au blocnote poar exemple  il vas te dire "le texte du fichier balbla a été blilbli voulez vous ...oui - non"  tu voi ce que je veux dire, et aussi par ce que aussi tu sais bien que ce message ne fait que détruire  une fenêtre mais pas les threads liée au application, alors si tu veux faire un meurtre professionnel sans terminateproccess ben! il faut nétoyé tous le secteur,.

Matrix
Messages postés
115
Date d'inscription
jeudi 17 avril 2008
Statut
Membre
Dernière intervention
16 juillet 2008

terminateproccess = kamikase, wm_close=Hitman
Matrix
Messages postés
1023
Date d'inscription
dimanche 1 août 2004
Statut
Membre
Dernière intervention
17 août 2008
2
@Rematrix: en effet, mais j'ai bien précisé dans mon post que cela n'étais valable que si tu avais une application Delphi VCL.
Dans le cas d'une app VCL, lorsque le FormClose s'exécute sur la fiche principale et valide la fermeture (note qu'il peut aussi ne pas valider la fermeture via un messagedlg si tu le souhaites) alors Delphi va:
- fermer la fenêtre
- laisser tous les gestionnaires d'évènements se finir
- quitter l'application.

Donc dans ce cas c'est suffisant.
Sinon, tu peux toujours te créer un message personnalisé qui évite de faire appel à la boîte de confirmation, si jamais tu es dans ce cas-là.
Messages postés
115
Date d'inscription
jeudi 17 avril 2008
Statut
Membre
Dernière intervention
16 juillet 2008

tu parlé peut etre de wm_endsession  ou de wm_queryendesession , pour moi j'ai éssayé juste de généralisé et j'ai pas fait attention a ton poste par ce que j'ai jamais du temps  libre alors j'éssay de participé comme même, mais c cool de ton on temps de recontré des gens qui s'interesse a des applications de type C.
Matrix