8
votes

Assurez-vous que toutes les méthodes Tthread.Queue complètent avant l'auto-destruct de fil

J'ai découvert que si une méthode en file d'attente avec tthread.queue appelle code> appelle une méthode qui invoque tapsplication.wndproc code> (par exemple, showMessage code>) puis suivant Les méthodes en file d'attente sont autorisées à exécuter avant la fin de la méthode d'origine. Pire encore, ils ne semblent pas être invoqués dans l'ordre de FIFO.

[EDIT: En fait, ils commencent dans l'ordre de FIFO. Avec ShowMessage CODE> On dirait que le plus tard, on a couru d'abord parce qu'il y a un appel à checksynchroniser code> avant l'apparition de la boîte de dialogue. Cela incombe à la méthode suivante et l'exécute, ne revenant pas avant la fin de la dernière méthode. Alors seulement, la boîte de dialogue apparaît.] P>

J'essaie de vous assurer que toutes les méthodes en file d'attente du thread de travailleur à exécuter dans le fil VCL exécuté dans une commande de FIFO stricte et qu'ils sont tous terminés avant le fil de travail est détruit. P>

Mon autre contrainte est que j'essaie de maintenir une séparation stricte de l'interface graphique de la logique commerciale. Le fil dans ce cas fait partie de la couche logique de l'entreprise afin que je ne puisse pas utiliser postmessage code> à partir d'un handliner code> pour organiser pour que le fil soit détruit (comme recommandé par un Nombre de contributeurs ailleurs). Donc, je règle frongonterminate: = true code> dans une méthode finale en file d'attente juste avant de tthread.execute sorties. (D'où la nécessité d'être exécutée dans un ordre de FIFO strict.) P>

Voici comment ma méthode de Tthread.execute se termine: P>

finally
  // Queue a final method to execute in the main thread that will set an event
  // allowing this thread to exit. This ensures that this thread can't exit
  // until all of the queued procedures have run.
  Queue(
    procedure
    begin
      if Assigned(fOnComplete) then
      begin
        fOnComplete(Self);
        // Handler sets fWorker.FreeOnTerminate := True and fWorker := nil
      end;
      SetEvent(fCanExit);
    end);
  WaitForSingleObject(fCanExit, INFINITE);
end;


9 commentaires

Semble que la file d'attente n'est pas le bon choix ici. Vous pouvez envisager d'utiliser la synchronisation à la place.


@Uweraabe J'aurais dû dire que les méthodes en file d'attente doivent être exécutées de manière asynchrone au fil du travailleur. Le thread du travailleur ne peut attendre qu'une fois qu'il a terminé tout son travail et qu'il est prêt à sortir.


IMHO L'exigence selon laquelle toutes les méthodes en file d'attente s'est terminée avant que le thread du travailleur soit libéré indique un couplage (trop) fort entre ce fil et les méthodes en file d'attente, qui sont exécutées dans le contexte d'un fil différent


@MJN Ceci se pose car il existe une exigence que le thread principal peut terminer le fil de travailleur à tout moment. Mais étant donné que le fil principal ne peut pas utiliser la technique postmessage habituelle pour libérer le fil de travailleur à partir de son gestionnaire à l'onduleur, nous devons utiliser Freonterminate: = True, mais cela ne peut pas être défini avant que toutes les méthodes en fileté soient terminées, car toutes les méthodes toujours dans la file d'attente Lorsque le fil est détruit, se détruit avec le fil. Mais je suis ouvert à d'autres moyens d'atteindre mes objectifs. Toutes les suggestions sont les bienvenues.


@mjm en fait, je pourrait utiliser PostMessage pour libérer le fil si le fil avait son propre WNDProc; Il est sans doute le seul moyen fiable de le faire de toute façon. Mais si j'utilise Windows Messaging pour cela, je pourrais aussi bien aller tout le porc et utiliser la messagerie de Windows au lieu de tthread.queue partout. Venez à cela, je pourrais aussi bien utiliser l'API Win32 natif plutôt que Ththread du tout. Mais je cherche la solution la plus simple qui fonctionne et cela semble stupide de ne pas exploiter le VCL.


@Iangoldby Avez-vous trouvé une meilleure solution pour cela? Je suis très intéressé par ça.


@ user15124 Voir la réponse acceptée ci-dessous. C'est la solution que j'ai adoptée.


Vous pouvez avoir un compteur dans chaque thread sur le nombre de messages où l'envoi de ce fil et attendez-vous 0 lorsque le thread quitte. si Freonterminate et fcounter = 0 puis quittez; qui nécessiterait un système de fil et de messagerie personnalisé cependant.


@ user15124 Je ne vois pas comment cela est différent de la solution que j'ai écrit, sauf que, avec votre suggestion sans objet de synchronisation, vous auriez à sonder jusqu'à ce que FCounter soit devenu zéro. L'utilisation d'un TcountDownEvent comme décrit évite ce problème.


4 Réponses :


6
votes

tthread.Queue () est une file d'attente FIFO. En fait, il partage la même file d'attente que thread.sychronise () utilise. Mais vous êtes correct que la manutention du message provoque l'exécution des méthodes en file d'attente. Ceci est parce que tapplication.idle () appels checksynchroniser () Chaque fois que la file d'attente de messages passe au ralenti après le traitement de nouveaux messages. Donc, si une méthode en file d'attente / synchronisée invoque le traitement des messages, cela peut permettre d'exécuter d'autres méthodes en file d'attente / synchronisées, même si la méthode précédente est toujours en cours d'exécution.

Si vous souhaitez vous assurer que une méthode de file d'attente est appelée avant la terminaison du three, vous devez utiliser synchroniser () au lieu de la file d'attente () ou utilisez le Onterminate événement à la place (qui est déclenché par synchroniser () ). Ce que vous faites dans votre enfin Le bloc est effectivement identique à celui de l'événement ONIMINER NATIVE.

Réglage Freonterminate: = true dans une méthode mise en file d'attente est de demander une fuite de mémoire. Freonterminate est évalué immédiatement lorsque Exécuter () Sorties, avant DoMerminate () est appelé pour déclencher l'événement (code> Ce qui est un superposition à mon avis, comme l'évaluait que précoce empêche à l'onduleur de décider au moment de la résiliation si un thread devrait se libérer ou non après à l'onduleur sorties). Donc, si la méthode en file d'attente fonctionne après Execute () a quitté, il n'y a aucune garantie que Freonterminate sera défini à temps. En attente d'une méthode mise en attente pour terminer avant de renvoyer le contrôle sur le thread est exactement ce que synchroniser () est destiné à. synchroniser () est synchrone, il attend que la méthode de sortie. file d'attente () est asynchrone, il n'attend pas du tout.


1 commentaires

J'ai lu votre autre réponse suggérant appelant à l'heure de synchronisation avant d'exécuter des sorties. Quand j'ai essayé cela, j'ai trouvé que l'on a semblé à exécuter avant les méthodes de la file d'attente () D, donc je supposais qu'il a sauté la file d'attente. Maintenant, je sais que c'était parce que les méthodes en file d'attente ont appelé la manipulation de messages et ce que j'ai écrit avec le serviforSingleObject est fonctionnellement équivalent à la synchronisation. Mais je suis toujours laissé avec le problème initial, comment obtenir le fil se libérer de lui-même, mais seulement après toutes les méthodes en file d'attente terminées, lorsque les méthodes en file d'attente pourraient elles-mêmes invoquer la manipulation des messages.



1
votes

C'est la solution que j'ai enfin adoptée.

J'ai utilisé un Delphi TCountDownEvent pour suivre le nombre de méthodes en file d'attente exceptionnelles à partir de mon thread, incrémentation du nombre de comptes juste avant de faire la queue d'une méthode et en la décrétant comme l'acte final de la méthode en file d'attente.

juste avant ma substitution de tthread.execute renvoie, il attend que le TCountDownEvent objet à signaler, c'est-à-dire lorsque le nombre atteint zéro. C'est l'étape cruciale qui garantit que toutes les méthodes en file d'attente ont terminé avant exécuter retours.

Une fois que toutes les méthodes en file d'attente sont complètes, il appelle synchroniser avec un OnComplete Handler - Grâce à Rémy pour indiquer que cela est équivalent à celui que mon code d'origine utilisait file d'attente et waitforsingleObject . ( OnComplete est comme à l'ondulate , mais appelé Avant d'exécuter les retours de sorte que le gestionnaire puisse modifier Freonterminate .)

Le seul Wrinkle est que tcountdowntevent.addcount ne fonctionne que si le nombre est déjà supérieur à zéro. Donc j'ai écrit une aide de classe à implémenter forceaddcount : xxx

Normalement, cela serait risqué, mais dans mon cas, nous savons qu'au moment du thread commence à attendre le nombre de méthodes en file d'attente en circulation pour atteindre zéro aucune autre méthode ne peut être mise en file d'attente (donc à partir de ce point une fois que le comte frappe zéro, il restera à zéro).

Ceci ne complètement < / EM> résolvez le problème des méthodes en file d'attente qui gérent les messages, dans lesquelles des méthodes en file d'attente individuelles pourraient toujours être à l'origine de l'ordre. Mais je dois maintenant avoir la garantie que toutes les méthodes en file d'attente fonctionnent de manière asynchrone mais seront complétées avant la sortie du fil. C'était l'objectif principal, car il permet au fil de se nettoyer sans risque de perdre des méthodes en file d'attente.


0 commentaires

-1
votes

Quelques réflexions:

  1. Freonterminate n'est pas la fin du monde si vous voulez que votre fil se soit supprimer.
  2. Semaphores vous permet de conserver un compte si vous ressentez le besoin, de telles constructions.
  3. Il n'y a rien à vous empêcher d'écrire ou d'utiliser vos propres primitives de file d'attente et allouerhwnd si vous voulez un contrôle grain fin.

0 commentaires

5
votes

J'ai corrigé ce problème en ajoutant un appel à synchroniser () code> à la fin de mon exécuté () code> méthode. Cela oblige le fil à attendre jusqu'à ce que tous les appels ajoutés avec la file d'attente () code> sont terminés sur le fil principal avant l'appel ajouté avec synchroniser () code> peut être appelé.

TMyThread = class (TThread)
private
  procedure QueueMethod;
  procedure DummySync;
protected
  procedure Execute; override;
end;

procedure TMyThread.QueueMethod;
begin
  // Do something on the main thread 
  UpdateSomething;
end;

procedure TMyThread.DummySync;
begin
  // You don't need to do anything here. It's just used
  // as a fence to stop the thread ending before all the 
  // Queued messages are processed.
end;

procedure TMyThread.Execute;
begin
  while SomeCondition do 
  begin
     // Some process

     Queue(QueueMethod);
  end;
  Synchronize(DummySync);
end;


1 commentaires

La solution la plus simple jamais.