2010-08-24 10 views
9

J'ai un thread principal et un thread séparé dans mon programme. Si le thread séparé finit avant le thread principal, il devrait se libérer automatiquement. Si le fil principal finit en premier, il doit libérer le fil séparé.Libérer un TThread automatiquement ou manuellement

Je connais FreeOnTerminate, et j'ai lu que vous devez faire attention à l'utiliser.

Ma question est, le code suivant est-il correct?

procedure TMyThread.Execute; 
begin 
    ... Do some processing 

    Synchronize(ThreadFinished); 

    if Terminated then exit; 

    FreeOnTerminate := true; 
end; 

procedure TMyThread.ThreadFinished; 
begin 
    MainForm.MyThreadReady := true; 
end; 

procedure TMainForm.Create; 
begin 
    MyThreadReady := false; 

    MyThread := TMyThread.Create(false); 
end; 

procedure TMainForm.Close; 
begin 
    if not MyThreadReady then 
    begin 
    MyThread.Terminate; 
    MyThread.WaitFor; 
    MyThread.Free; 
    end; 
end; 

Répondre

8

Vous pouvez simplifier cela:

procedure TMyThread.Execute; 
begin 
    // ... Do some processing 
end; 

procedure TMainForm.Create; 
begin 
    MyThread := TMyThread.Create(false); 
end; 

procedure TMainForm.Close; 
begin 
    if Assigned(MyThread) then 
    MyThread.Terminate; 
    MyThread.Free; 
end; 

Explication:

  • Soit utiliser FreeOnTerminate ou sans fil manuellement, mais jamais les deux. La nature asynchrone de l'exécution du thread signifie que vous risquez de ne pas libérer le thread ou (pire encore) de le faire deux fois. Il n'y a aucun risque à garder l'objet thread autour après avoir terminé l'exécution, et il n'y a aucun risque à appeler Terminate() sur un thread qui a déjà terminé soit.

  • Il n'est pas nécessaire de synchroniser l'accès à un booléen écrit uniquement à partir d'un thread et lu par un autre. Dans le pire des cas, vous obtenez la mauvaise valeur, mais en raison de l'exécution asynchrone qui est un effet parasite de toute façon. La synchronisation n'est nécessaire que pour les données qui ne peuvent pas être lues ou écrites de manière atomique. Et si vous avez besoin de synchroniser, n'utilisez pas Synchronize() pour cela.

  • Il n'est pas nécessaire d'avoir une variable similaire à MyThreadReady, car vous pouvez utiliser WaitForSingleObject() pour interroger l'état d'un thread. Passez MyThread.Handle comme premier et 0 comme second paramètre, et vérifiez si le résultat est WAIT_OBJECT_0 - si c'est le cas, votre thread a terminé l'exécution.

BTW: Ne pas utiliser l'événement OnClose, utilisez OnDestroy à la place. Le premier n'est pas nécessairement appelé, auquel cas votre thread pourrait continuer à fonctionner et garder votre processus en vie.

+0

Bonjour! FreeOnTerminate seul n'est pas une option. D'un autre côté, je ne veux pas que le thread enregistre de la mémoire pendant l'exécution du programme Main. Je synchronise le booléen parce que je crois qu'il garantit qu'il s'exécutera avant MainForm.Close ou après MainForm.Close. Donc, MyThread.Terminate ne sera appelé que si FreeOnTerminate est faux. Est-ce que je me trompe ici? – Steve

+0

Quelle est la quantité de mémoire que le thread 'hog' une fois terminé? Si vous ne pouvez pas dire que vous n'avez aucune raison de vous inquiéter. Mesurez d'abord. Mais si vous insistez, publiez un message de votre thread comme dernière chose et libérez le thread dans le gestionnaire de messages. 'Synchronize()' est trop vil pour penser même si votre code fonctionnerait dans toutes les circonstances. Dites simplement non. – mghie

+0

Accepter cela comme la solution car c'est la plus propre. Merci à tous pour les réponses! – Steve

2

Non, votre code n'est pas bon (mais il ne fonctionnera probablement à 99,99%, voire 100% des cas). Si vous prévoyez de terminer le thread de travail à partir du thread principal, ne définissez pas FreeOnTerminate sur True (je ne vois pas ce que vous essayez de gagner dans le code ci-dessus en définissant FreeOnTerminate sur True, au moins rend votre code moins compréhensible) .

Une situation plus importante avec les threads de travail de fin est que vous essayez de fermer une application pendant que le thread de travail est en attente. Le thread ne sera pas éveillé si vous appelez simplement Terminate. En général, vous devez utiliser un objet de synchronisation supplémentaire (généralement un événement) pour réveiller le thread de travail.

Et une remarque - il n'y a pas besoin de

begin 
    MyThread.Terminate; 
    MyThread.WaitFor; 
    MyThread.Free; 
    end; 

si vous regardez le code TThread.Destroy, il appelle Terminate et WaitFor, si

MyThread.Free; 

est suffisant (au moins en Delphi 2009, n'ont pas de sources Delphi 7 à portée de main pour vérifier).


Mise à jour

Lire la réponse de mghie. Considérez la situation suivante (mieux sur 1 système CPU):

thread principal

exécute

procedure TMainForm.Close; 
begin 
    if not MyThreadReady then 
    begin 
    MyThread.Terminate; 
    MyThread.WaitFor; 
    MyThread.Free; 
    end; 
end; 

il vérifie la valeur MyThreadReady (il est faux) et a été éteint par programmateur.

Maintenant, le planificateur passe au thread de travail; il exécute

Synchronize(ThreadFinished); 

et force le planificateur à revenir au thread principal. Le thread principal continue l'exécution:

MyThread.Terminate; // no problem 
    MyThread.WaitFor;  // ??? 
    MyThread.Free; 

pouvez-vous dire ce qui se passera à WaitFor? Je ne peux pas (exige un regard plus profond dans les sources de TThread pour répondre, mais à première vue ressemble à une impasse).

Votre véritable erreur est quelque chose de différent - vous avez écrit un code non fiable et en essayant de savoir si c'est correct ou non. C'est une mauvaise pratique avec les threads - vous devriez apprendre à écrire un code fiable à la place. En ce qui concerne les ressources - lorsque TThread (avec FreeOnTerminate = False) est terminé, les seules ressources qui restent allouées sont le gestionnaire de thread Windows (il n'utilise pas de ressources Windows substantielles après la fin du thread) et l'objet Delphi TThread en mémoire. Pas un gros coût pour être du bon côté.

+0

Si je ne mets pas FreeOnTerminate à true, le thread ne sera pas libéré tant que le thread principal n'aura pas fini. Les minutes ou les heures peuvent passer jusqu'à ce que les ressources du fil séparé soient libérées - je ne le veux pas. Je ne comprends pas ce que vous voulez dire dans le deuxième paragraphe de votre réponse. Dans quel cas le code ci-dessus ne fonctionnera-t-il pas? Quel est ce 00.01%? – Steve

4

Le thread principal affecte un gestionnaire à l'événement OnTerminate du thread de travail. Si le thread de travail se termine en premier, le gestionnaire peut signaler au thread principal de libérer le thread. Si le thread principal finit en premier, il peut terminer le thread de travail. Par exemple:

procedure TMyThread.Execute; 
begin 
    ... Do some processing ... 
end; 

procedure TMainForm.Create; 
begin 
    MyThread := TMyThread.Create(True); 
    MyThread.OnTerminate := ThreadFinished; 
    MyThread.Resume; // or MyThread.Start; in D2010+ 
end; 

const 
    APPWM_FREE_THREAD = WM_APP+1; 

procedure TMainForm.ThreadFinished(Sender: TObject); 
begin 
    PostMessage(Handle, APPWM_FREE_THREAD, 0, 0); 
end; 

procedure TMainForm.WndProc(var Message: TMessage); 
begin 
    if Message.Msg = APPWM_FREE_THREAD then 
    StopWorkerThread 
    else 
    inherited; 
end; 

procedure TMainForm.StopWorkerThread; 
begin 
    if MyThread <> nil then 
    begin 
    MyThread.Terminate; 
    MyThread.WaitFor; 
    FreeAndNil(MyThread); 
    end; 
end; 

procedure TMainForm.Close; 
begin 
    StopWorkerThread; 
end; 
+0

Je ne l'aime pas. Au lieu de supprimer le problème, vous le cachez plus profondément, il est donc de plus en plus difficile de savoir où le code peut échouer. – kludg

+2

Non, je ne cache rien. C'est un moyen beaucoup plus efficace de résoudre le problème original, et cela fonctionne très bien. Le thread de travail ne devrait pas essayer de se libérer directement du tout. Puisque le thread principal est celui qui démarre le thread, et qu'il a besoin de gérer le thread de toute façon, il devrait être celui qui libère le thread. TThread a un événement OnTerminate spécifiquement dans le but de notifier un autre code lorsque le thread se termine, et accomplit la même chose que l'appel 'Synchronize (ThreadFinished)' d'origine faisait ... ... –

+2

... The PostMessage () est juste de retarder la libération réelle du thread quelques millisecondes, car il ne peut pas être effectué dans l'événement OnTerminate lui-même. Mais le thread de travail est toujours auto-libéré quand il se termine, il n'est juste pas libéré de l'intérieur du thread lui-même, c'est la seule différence. –

0

Honnêtement, votre


... Do some processing 

est le vrai problème ici. Est-ce une boucle pour faire quelque chose de manière récursive? Si ce n'est pas le cas, vous devriez envisager de diviser cette tâche en petites procédures/fonctions, et de les mettre ensemble dans le corps de l'exécution, en appelant l'une après l'autre avec conditionnel, comme:



While not Terminated do 
begin 

    if MyThreadReady then 
    DoStepOneToTaskCompletion 
    else 
    clean_and_or_rollback(Something Initialized?); 

    if MyThreadReady then 
    DoStepTwoToTaskCompletion 
    else 
    clean_and_or_rollback(Something Initialized?, StepOne); 

    if MyThreadReady then 
    DoStepThreeToTaskCompletion 
    else 
    clean_and_or_rollback(Something Initialized?, StepOne, StepTwo); 

    Self.DoTerminate; // Not sure what to expect from that one 
end; 

Il est sale, presque un hack, mais fonctionnera comme prévu.

À propos FreeOnTerminate, eh bien ... il suffit de retirer la déclaration et toujours


FreeAndNil(ThreadObject); 

Je ne suis pas fan de syncronise. J'aime les sections plus critiques, pour la flexibilité d'étendre le code pour gérer plus de données partagées.

Sur le formulaire section publique, déclare:

ControlSection : TRTLCriticalSection; 

Sur la forme ou créer un autre avant fil.créer,

InitializeCriticalSection(ControlSection); 

Ensuite, chaque fois que vous écrivez à une ressource partagée (y compris votre variable MyThreadReady), ne


EnterCriticalSection (ControlSection); 
    MyThreadReady := True; //or false, or whatever else 
LeaveCriticalSection (ControlSection); 

Avant d'aller (sortie), appelez


DeleteCriticalSection (ControlSection); 

et libérez votre fil comme vous le faites toujours.

Cordialement Rafael

0

Je dire que le mélange des modèles est tout simplement pas recommandé. Vous utilisez FreeOnTerminate et ne plus jamais toucher le fil, ou vous ne le faites pas. Sinon, vous avez besoin d'un moyen protégé pour que les deux puissent communiquer.

Étant donné que vous souhaitez un contrôle précis de la variable de fil, n'utilisez pas FreeOnTerminate. Si votre thread se termine tôt, effacez les ressources locales que le thread a consommées comme vous le feriez normalement, puis laissez simplement le thread principal libérer le thread enfant lorsque l'application est terminée. Vous obtiendrez le meilleur des deux mondes: les ressources libérées par le thread enfant dès que possible, et vous n'aurez plus à vous soucier de la synchronisation des threads. (Et il a le bonus supplémentaire d'être beaucoup plus simple dans design/code/understanding/support ...)