IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Lazarus Pascal Discussion :

[2.0.12] Besoin d'éclaircissement sur les threads


Sujet :

Lazarus Pascal

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Expert confirmé
    Avatar de Jipété
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    11 132
    Détails du profil
    Informations personnelles :
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 11 132
    Par défaut
    Alors ça n'a pas trainé : après avoir complété avec les bouts qui manquaient (des broutilles), j'exécute en pas à pas et après le FormCreate, bim bam boum :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    procedure TThreadA.Execute;
    var
      ThreadB :TThreadB;
    begin
      ThreadB := TThreadB.Create(False);
    
      try
        while not Terminated do
        begin
          // Prépare B
          RTLEventResetEvent(WaitFinish);
          // Attend Start
          RtlEventWaitFor(WaitRunning); // SIGSEGV
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Le projet waitforexample1 a levé une classe d''exception "External: SIGSEGV".
     dans le fichier "../nptl/pthread_mutex_lock.c" à la ligne 67
    Fichier qui n'existe pas, je le rappelle :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $ locate pthread_mutex_lock.c
    $ (vide)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Le fichier "nptl/pthread_mutex_lock.c" n''a pas été trouvé.
    Souhaitez-vous le rechercher manuellement ?
    Je lui dis "non" et il m'envoie ça, que je ne sais pas décoder :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    ../nptl/pthread_mutex_lock.c:423          
    00007FFFF7F97738 488d0d31c00000           lea    0xc031(%rip),%rcx        # 0x7ffff7fa3770 <__PRETTY_FUNCTION__.1>
    00007FFFF7F9773F baa7010000               mov    $0x1a7,%edx
    00007FFFF7F97744 488d35afbe0000           lea    0xbeaf(%rip),%rsi        # 0x7ffff7fa35fa
    00007FFFF7F9774B 488d3ddebd0000           lea    0xbdde(%rip),%rdi        # 0x7ffff7fa3530
    00007FFFF7F97752 e869baffff               call   0x7ffff7f931c0 <__assert_fail@plt>
    00007FFFF7F97757 660f1f840000000000       nopw   0x0(%rax,%rax,1)
    ../nptl/pthread_mutex_lock.c:67           
    00007FFFF7F97760 8b4710                   mov    0x10(%rdi),%eax
    00007FFFF7F97763 89c2                     mov    %eax,%edx
    00007FFFF7F97765 81e27f010000             and    $0x17f,%edx
    ../nptl/pthread_mutex_lock.c:71           
    00007FFFF7F9776B 83e07c                   and    $0x7c,%eax
    00007FFFF7F9776E 0f857c000000             jne    0x7ffff7f977f0 <__GI___pthread_mutex_lock+144>
    Je n'ai pas tout mis, et c'est la ligne en gras qui est pointée comme fautive, dans la fenêtre qui s'affiche, et là, je suis un peu comme une poule qui aurait trouvé un couteau...

    Question subsidiaire : si le fichier n'existe pas, d'où sort ce dump ?

  2. #2
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 934
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 934
    Par défaut
    Citation Envoyé par Jipété Voir le message
    Question subsidiaire : si le fichier n'existe pas, d'où sort ce dump ?
    Il râle sans doute parce qu'il ne peut pas afficher la source mais la version compilée est bien présente.

    Et pour l'exception, tu as bien créé les deux mutex dans initialization ?

  3. #3
    Expert confirmé
    Avatar de Jipété
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    11 132
    Détails du profil
    Informations personnelles :
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 11 132
    Par défaut
    Citation Envoyé par Andnotor Voir le message
    Il râle sans doute parce qu'il ne peut pas afficher la source mais la version compilée est bien présente.
    Merci pour cette précision lumineuse.

    Citation Envoyé par Andnotor Voir le message
    Et pour l'exception, tu as bien créé les deux mutex dans initialization ?
    Même pas, je suis très fort en copier/coller :
    Citation Envoyé par Andnotor Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    initialization
      WaitRunning := RTLEventCreate;
      WaitFinish  := RTLEventCreate;
     
    finalization
      RTLEventDestroy(WaitRunning);
      RTLEventDestroy(WaitFinish);
    Et d'ailleurs, j'ai une question : il vaut mieux faire comme ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var
      Form1: TForm1;
      WaitRunning :PRTLEvent;
      WaitFinish  :PRTLEvent;
     
    implementation
     
    {$R *.lfm}
     
    {var
      WaitRunning :PRTLEvent;
      WaitFinish  :PRTLEvent;   }
    ou comme cela :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var
      Form1: TForm1;
    //  WaitRunning :PRTLEvent;
    //  WaitFinish  :PRTLEvent;
     
    implementation
     
    {$R *.lfm}
     
    var
      WaitRunning :PRTLEvent;
      WaitFinish  :PRTLEvent;
    ?

    EDIT : et une petite dernière, pour la nuit :
    dans
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      RTLEventSetEvent(WaitRunning);
      ThreadA.Free;
    end;
    tu mets un RTLEventSetEvent, pourquoi pas un RTLEventReset ? /EDIT

    En tout état de cause, dans un cas comme dans l'autre, le code ne fonctionne plus...
    Nom : erreur_du_débogueur.png
Affichages : 325
Taille : 37,0 Ko

    Dès F9 je me prends ça dans les dents et je ne sais pas ce que ça veut dire (google non plus), et que faire ?
    Dans les options générales j'ai coché "réinitialiser le compilateur après chaque utilisation" et je vide les fichiers temporaires dans le dossier lib du projet mais ce truc s'est imposé peu après mes premiers essais avec ta nouvelle proposition de code et je ne m'en débarrasse pas,

    Pour info, un clic sur ? Plus affiche une fenêtre pleine de code assembleur, au secours !

  4. #4
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 934
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 934
    Par défaut
    Citation Envoyé par Jipété Voir le message
    Et d'ailleurs, j'ai une question : il vaut mieux faire comme ceci :
    ou comme cela :
    Les deux vont mais puisque ces variables ne sont utilisées que dans cette unité, autant en limiter la visibilité à cette unité.

    Citation Envoyé par Jipété Voir le message
    tu mets un RTLEventSetEvent, pourquoi pas un RTLEventResetEvent ?
    Parce que les threads sont en attente sur WaitFor. Il faut signaler le mutex pour permettre la sortie sur Terminated sinon le processus risque de ne pas être déchargé (le programme ne s'arrête pas).

    Citation Envoyé par Jipété Voir le message
    Dès F9 je me prends ça dans les dents
    Peut-être par mégarde as-tu mis un point d'arrêt depuis la fenêtre CPU. Ctrl+Alt+B (sous Delphi tout du moins) pour afficher la liste et le supprimer.

    Si c'est pas ça, ben je peux pas t'aider plus avec l'EDI.

  5. #5
    Expert confirmé
    Avatar de Jipété
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    11 132
    Détails du profil
    Informations personnelles :
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 11 132
    Par défaut
    Bonjour,

    et grand merci pour ton implication, et tes explications.

    Vu pour le var avant ou après implementation, allez, je prends après.

    Pour le plantage du déboggueur au lancement, alors là, j'étais à des années-lumière de trouver la solution, je ne mets jamais mes mains là-dedans et pourtant, la solution était là :

    Nom : points_darrêt-v2.png
Affichages : 325
Taille : 24,2 Ko

    Comment est-ce arrivé ? Grand mystère...

    Quant à l'appli, il doit nous manquer un petit bout parce que ça ne va pas plus loin :

    Nom : 1seule-itération.png
Affichages : 321
Taille : 7,9 Ko

    Le bouton "Stop" fonctionne car j'ai rajouté une ligne depuis le début de cette aventure :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    procedure TForm1.btnStopClick(Sender: TObject);
    begin
      RTLEventResetEvent(WaitRunning);
      Memo1.SetFocus; // juste pour vérifier que le prog n'est pas planté
    end;
    et je vois bien le curseur clignoter dans le memo après le clic du bouton (et il est même présent sur la copie d'écran).

    Là où c'est plus tendu, c'est la clôture de l'appli : un clic sur la croix m'envoie une erreur SIGTERM après une fenêtre "le programme semble ne pas répondre. Voulez-vous blabla..." et il faut que je le termine avec Ctrl-F2 car sinon, il continue à tourner en tâche de fond...

    Je précise que c'est strictement ton code, à une ou deux bricolettes près, genre dans ThreadB.Execute,
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
        // Signale la fin
        RTLEventSetEvent(ThreadB.WaitFinish);
    Citation Envoyé par fenêtre de messages
    Error: Identifier not found "ThreadB"
    alors
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
        // Signale la fin
        RTLEventSetEvent(WaitFinish);
    passe bien.
    Ou
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    procedure TThreadA.Execute;
    var
      ThreadB :TThreadB;
    begin
      ThreadB := TThreadB.Create;
    qui devient
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    procedure TThreadA.Execute;
    var
      ThreadB :TThreadB;
    begin
      ThreadB := TThreadB.Create(False);
    et pareil pour le ThreadA dans le FormCreate.
    Des broutilles, te dis-je.

    Mais j'avais noté un truc lors de mes premiers essais, des fois que ça serait une piste ? :
    Dans le ThreadB.Execute,
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
      while not Terminated do
      begin
        // Attend Start
        RtlEventWaitFor(WaitRunning); // andnotor
        //RtlEventWaitFor(Form1.ThreadA.WaitRunning); // si décommenté, la suite dessous n'est pas affichée
    donc je commente ta ligne mais là, l'affichage devient, sans appuyer sur "Start" !,

    Nom : avec_ligne_commentée.png
Affichages : 315
Taille : 20,4 Ko

    ad nauseam...
    Le truc sympa, c'est qu'un clic sur la croix ferme bien le prog et heureusement car le bouton "Stop" devient inopérant.

    Une idée ?

    EDIT
    PS : un truc de malade, l'essai que je viens de faire après avoir posté : comme ça me gavait que le prog démarre tout seul en commentant la ligne du ThreadB.Execute que j'ai montrée, j'ai pris sur moi de tenter ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      ThreadA := TThreadA.Create(True); // false à l'origine
    end;
     
    procedure TForm1.btnStartClick(Sender: TObject);
    begin
      RTLEventSetEvent(WaitRunning);
      if ThreadA.Suspended then ThreadA.Start; // nouvelle ligne
    end;
    et en conservant la ligne commentée, ça ne démarre que si je clique sur Start, et ça affiche ça :

    Nom : suspended_true.png
Affichages : 319
Taille : 13,1 Ko

    Mais le bouton Stop est toujours inopérant, et la croix de fermeture fonctionne bien.

    Il nous manque des bouts, on dirait qu'il faudrait plus de témoins de passage ici et là.
    Un brave sac de nœuds, hein...
    /EDIT

  6. #6
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 934
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 934
    Par défaut
    Citation Envoyé par Jipété Voir le message
    Quant à l'appli, il doit nous manquer un petit bout parce que ça ne va pas plus loin
    Tout est conditionné par WaitRunning. Tu as donc encore un RTLEventResetEvent(WaitRunning) qui traine dans les threads.

    A mettre uniquement dans le bouton Stop.

    Citation Envoyé par Jipété Voir le message
    "le programme semble ne pas répondre. Voulez-vous blabla..."
    Si tu as bien mis RTLEventSetEvent(WaitRunning) dans Destroy, voir plus bas.

    Citation Envoyé par Jipété Voir le message
    Je précise que c'est strictement ton code
    Ils disent tous ça

    Et oui RTLEventSetEvent(ThreadB.WaitFinish) est un erreur de copier/coller. Je n'ai pas Lazarus, je n'ai pas testé !

    Et pour revenir au "blabla" ci-dessus, as-tu toujours un sleep dans ThreadB ?
    Si ce n'est plus le cas, la tâche principale passe sont temps à traiter les Synchonize (les logs) et n'a plus le temps de vider la pile de messages => le bouton Stop ne fonctionne plus. Laisse au moins un Sleep(1).

    Mais ce serait bien mieux de travailler par message (PostMessage) plutôt que par Synchonize.

    Suspend/Resume est à éviter absolument puisqu'une nouvelle fois tu ne maitrises pas l'exécution du thread, tu ne sais pas sur quelle ligne la suspension va intervenir, contrairement à un WaitFor. Et si un verrou a été activé (section critique, mutex, etc.) les autres threads l'utilisant se retrouveront en deadlock, y compris la tâche principale si elle l'utilise => Game over !

  7. #7
    Expert confirmé
    Avatar de Jipété
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    11 132
    Détails du profil
    Informations personnelles :
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 11 132
    Par défaut
    Citation Envoyé par Andnotor Voir le message
    Tout est conditionné par WaitRunning. Tu as donc encore un RTLEventResetEvent(WaitRunning) qui traine dans les threads.

    A mettre uniquement dans le bouton Stop.
    Et c'est bien comme ça que c'est : j'ai fait une recherche sur RTLEventResetEvent seulement et ça ne me remonte que deux lignes :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    procedure TForm1.btnStopClick(Sender: TObject);
    begin
      RTLEventResetEvent(WaitRunning);
      Memo1.SetFocus; // juste pour vérifier que le prog n'est pas planté
    end;
     
    procedure TThreadA.Execute;
    var
      ThreadB :TThreadB;
    begin
      ThreadB := TThreadB.Create(False);
     
      try
        while not Terminated do
        begin
          // Prépare B
          RTLEventResetEvent(WaitFinish);
    Citation Envoyé par Andnotor Voir le message
    Si tu as bien mis RTLEventSetEvent(WaitRunning) dans Destroy, voir plus bas.
    C'est bien le cas :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      RTLEventSetEvent(WaitRunning);
      ThreadA.Free;
    end;
    Citation Envoyé par Andnotor Voir le message
    Et pour revenir au "blabla" ci-dessus, as-tu toujours un sleep dans ThreadB ?
    Il y est depuis le début de l’histoire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    procedure TThreadB.Execute;
    var
      i: Integer;
    begin
      while not Terminated do
      begin
        // Attend Start
        RtlEventWaitFor(WaitRunning);
        //RtlEventWaitFor(Form1.ThreadA.WaitRunning); // si décommenté, dessous pas affiché
     
        Log('B: Working ...');
        for i := 1 to 5 do begin
          Sleep(300);
          Log('.....  ', False);
        end;
        Log('');
        Counter := Counter + 1;
        Log('B: Wake A');
        // wake A
     
        // Signale la fin
        RTLEventSetEvent(WaitFinish);
      end;
    end;
    Citation Envoyé par Andnotor Voir le message
    Mais ce serait bien mieux de travailler par message (PostMessage) plutôt que par Synchonize.
    Va falloir en parler aux gens de Lazarus,

    Bon, je suis ennuyé mais va falloir que je m'absente, vers 14 h 30 - 15 h, retour estimé vers 18 h...
    Merci pour ta patience.

    Tiens, je te mets (presque) tout le code, tu reconnaitras tes phrases :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    {$R *.lfm}
     
    var
      WaitRunning : PRTLEvent;
      WaitFinish  : PRTLEvent;
     
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      ThreadA := TThreadA.Create(False);
    end;
     
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      RTLEventSetEvent(WaitRunning);
      ThreadA.Free;
    end;
     
    procedure TForm1.btnStartClick(Sender: TObject);
    begin
      RTLEventSetEvent(WaitRunning);
    end;
     
    procedure TForm1.btnStopClick(Sender: TObject);
    begin
      RTLEventResetEvent(WaitRunning);
      Memo1.SetFocus; // juste pour vérifier que le prog n'est pas planté
    end;
     
    { TThreadA }
    procedure TThreadA.Execute;
    var
      ThreadB :TThreadB;
    begin
      ThreadB := TThreadB.Create(False);
     
      try
        while not Terminated do
        begin
          // Prépare B
          RTLEventResetEvent(WaitFinish);
          // Attend Start
          RtlEventWaitFor(WaitRunning);
     
          Log('A: wait for B ...');
          // wait infinitely (until B réveille A)
          Log('A: ThreadB.Counter=' + IntToStr(ThreadB.Counter));
     
          // Attend la fin de B
          RtlEventWaitFor(WaitFinish);
     
          // B terminé !
        end;
     
      finally
        ThreadB.Free;
      end;
    end;
     
    { TThreadB }
    procedure TThreadB.Execute;
    var
      i: Integer;
    begin
      while not Terminated do
      begin
        // Attend Start
        RtlEventWaitFor(WaitRunning);
        //RtlEventWaitFor(Form1.ThreadA.WaitRunning); // si décommenté, dessous pas affiché
     
        Log('B: Working ...');
        for i := 1 to 5 do begin
          Sleep(300);
          Log('.....  ', False);
        end;
        Log('');
        Counter := Counter + 1;
        Log('B: Wake A');
        // wake A
     
        // Signale la fin
        RTLEventSetEvent(WaitFinish);
      end;
    end;
     
    procedure TForm1.AddMessage;
    begin
      Memo1.Lines.Text := MsgText;
      Memo1.SelStart := length(Memo1.Text);
    end;
     
    { TThreadForTasks }
    procedure TThreadForLog.Log(const Msg: string; AppendLineEnd: boolean);
    var
      s: String;
    begin
      EnterCriticalsection(Form1.ACriticalSection);
      s := Msg;
      if AppendLineEnd then s := s + LineEnding;
      dbgout(s);
      Form1.MsgText := Form1.MsgText + s;
      Synchronize(@Form1.AddMessage);
      LeaveCriticalsection(Form1.ACriticalSection);
    end;
     
    initialization
      WaitRunning := RTLEventCreate;
      WaitFinish  := RTLEventCreate;
     
    finalization
      RTLEventDestroy(WaitRunning);
      RTLEventDestroy(WaitFinish);
    end.

  8. #8
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 934
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 934
    Par défaut
    Bon ben le problème vient alors de RtlEventWaitFor qui reset l'event. L' API Windows nous laisse le choix d'un reset manuel ou automatique. Pas Free Pascal manifestement, dommage !

    Il faudrait donc simuler cela par une section critique. WaitRunning est conservé mais uniquement pour démarrer ThreadB.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    var
      CSRunning   : TCriticalSection;
      WaitRunning : PRTLEvent;
      WaitFinish  : PRTLEvent;
     
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      ThreadA := TThreadA.Create(False);
    end;
     
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      LeaveCriticalSection(CSRunning);
      ThreadA.Free;
    end;
     
    procedure TForm1.btnStartClick(Sender: TObject);
    begin
      LeaveCriticalSection(CSRunning);
    end;
     
    procedure TForm1.btnStopClick(Sender: TObject);
    begin
      EnterCriticalsection(CSRunning);
      Memo1.SetFocus; // juste pour vérifier que le prog n'est pas planté
    end;
     
    { TThreadA }
    procedure TThreadA.Execute;
    var
      ThreadB :TThreadB;
    begin
      ThreadB := TThreadB.Create(False);
     
      try
        while not Terminated do
        begin
          // Attend Start (mais ne conserve pas le verrou)
          EnterCriticalsection(CSRunning);
          LeaveCriticalSection(CSRunning);
     
          RTLEventSetEvent(WaitRunning);
     
          Log('A: wait for B ...');
     
          // Attend la fin de B
          RtlEventWaitFor(WaitFinish);
          Log('A: ThreadB.Counter=' + IntToStr(ThreadB.Counter));
     
          // B terminé !
        end;
     
      finally
        ThreadB.Free;
      end;
    end;
     
    { TThreadB }
    procedure TThreadB.Execute;
    var
      i: Integer;
    begin
      while not Terminated do
      begin
        // Attend Start
        RtlEventWaitFor(WaitRunning);
     
        Log('B: Working ...');
        for i := 1 to 5 do begin
          Sleep(300);
          Log('.....  ', False);
        end;
        Log('');
        Counter := Counter + 1;
        Log('B: Wake A');
     
        // Signale la fin
        RTLEventSetEvent(WaitFinish);
      end;
    end;
     
    procedure TForm1.AddMessage;
    begin
      Memo1.Lines.Text := MsgText;
      Memo1.SelStart := length(Memo1.Text);
    end;
     
    { TThreadForTasks }
    procedure TThreadForLog.Log(const Msg: string; AppendLineEnd: boolean);
    var
      s: String;
    begin
      EnterCriticalsection(Form1.ACriticalSection);
      s := Msg;
      if AppendLineEnd then s := s + LineEnding;
      dbgout(s);
      Form1.MsgText := Form1.MsgText + s;
      Synchronize(@Form1.AddMessage);
      LeaveCriticalsection(Form1.ACriticalSection);
    end;
     
    initialization
      InitCriticalSection(CSRunning);
      WaitRunning := RTLEventCreate;
      WaitFinish  := RTLEventCreate;
     
    finalization
      DoneCriticalSection(CSRunning);
      RTLEventDestroy(WaitRunning);
      RTLEventDestroy(WaitFinish);
    end.
    Citation Envoyé par Jipété Voir le message
    Va falloir en parler aux gens de Lazarus
    Oui mais non !

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    const
      LM_LOG     = LM_USER;
      A_WaitForB = 1;
      A_Counter  = 2;
      B_Working  = 3;
      B_Running  = 4;
      B_WakeA    = 5;
     
    type
      TForm1 = class(TForm)
        ...
      private
        ...
        procedure LMLog(var Message :TMessage); message LM_LOG;
      end;
     
    implementation
     
    var
      Wnd         : THandle;
      CSRunning   : TCriticalSection;
      WaitRunning : PRTLEvent;
      WaitFinish  : PRTLEvent;
     
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      ThreadA := TThreadA.Create(False);
      Wnd := Handle;
    end;
     
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      LeaveCriticalSection(CSRunning);
      ThreadA.Free;
    end;
     
    procedure TForm1.LMLog(var Message :TMessage);
    begin
      case Message.WParam of
        A_WaitForB : Memo1.Lines.Add('A: wait for B ...');
        A_Counter  : Memo1.Lines.Add(Format('A: ThreadB.Counter=%d', [Message.LParam]));
        B_Working  : Memo1.Lines.Add('B: Working ...');
        B_Running  : Memo1.Lines[Memo1.Lines.Count -1] := Memo1.Lines[Memo1.Lines.Count -1] +' ...';
        B_WakeA    : Memo1.Lines.Add('B: Wake A');
      end;
    end;
     
    procedure TForm1.btnStartClick(Sender: TObject);
    begin
      LeaveCriticalSection(CSRunning);
    end;
     
    procedure TForm1.btnStopClick(Sender: TObject);
    begin
      EnterCriticalsection(CSRunning);
      Memo1.SetFocus; // juste pour vérifier que le prog n'est pas planté
    end;
     
    { TThreadA }
    procedure TThreadA.Execute;
    var
      ThreadB :TThreadB;
    begin
      ThreadB := TThreadB.Create(False);
     
      try
        while not Terminated do
        begin
          // Attend Start (mais ne conserve pas le verrou)
          EnterCriticalsection(CSRunning);
          LeaveCriticalSection(CSRunning);
     
          RTLEventSetEvent(WaitRunning);
     
          // Attend la fin de B
          PostMessage(Wnd, LM_LOG, A_WaitForB, 0);
          RtlEventWaitFor(WaitFinish);
          PostMessage(Wnd, LM_LOG, A_Counter, ThreadB.Counter);
          // B terminé !
        end;
     
      finally
        ThreadB.Free;
      end;
    end;
     
    { TThreadB }
    procedure TThreadB.Execute;
    var
      i: Integer;
    begin
      while not Terminated do
      begin
        // Attend Start
        RtlEventWaitFor(WaitRunning);
     
        PostMessage(Wnd, LM_LOG, B_Working, 0);
        for i := 1 to 5 do begin
          Sleep(300);
          PostMessage(Wnd, LM_LOG, B_Running, 0);
        end;
     
        Counter := Counter + 1;
     
        // Signale la fin
        PostMessage(Wnd, LM_LOG, B_WakeA, 1);
        RTLEventSetEvent(WaitFinish);
      end;
    end;
     
    initialization
      InitCriticalSection(CSRunning);
      WaitRunning := RTLEventCreate;
      WaitFinish  := RTLEventCreate;
     
    finalization
      DoneCriticalSection(CSRunning);
      RTLEventDestroy(WaitRunning);
      RTLEventDestroy(WaitFinish);
     
    end.

  9. #9
    Expert confirmé
    Avatar de Jipété
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    11 132
    Détails du profil
    Informations personnelles :
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 11 132
    Par défaut
    Suis rentré plus tôt que prévu (j'étais impatient, )

    Première version du code : ça fonctionne sur trois pattes dans le sens où
    1- l'affichage démarre sans cliquer sur Start, bizarre bizarre ;
    2- si Stop fonctionne, la croix de fermeture génère un SIGTERM.

    Et j'ai des questions :
    - comment est-il possible que le mémo se remplisse sans avoir cliqué sur Start ?
    - que veut dire, dans TTheadA.Execute, le bloc
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
          // Attend Start (mais ne conserve pas le verrou)
          EnterCriticalsection(CSRunning);
          LeaveCriticalSection(CSRunning);
    Pour moi, un truc qui entre et sort aussitôt, il ne fait rien. S'il doit attendre Start, entre l'entrée et la sortie il devrait y avoir une boucle d'attente, avec un sleep(xxx) et/ou un WaitFor ou similaire, non ? Ou alors cette entrée dans la critical section c'est bloquant ? Je pose la question parce qu'il n'y a pas d'aide sur ce mot-clé (La misère, l'aide de Lazarus...)
    - et ça, ça défie mon bon sens :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    procedure TForm1.btnStartClick(Sender: TObject);
    begin
      LeaveCriticalSection(CSRunning);
    end;
     
    procedure TForm1.btnStopClick(Sender: TObject);
    begin
      EnterCriticalSection(CSRunning);
      Memo1.SetFocus; // juste pour vérifier que le prog n'est pas planté
    end;
    Pour moi, quand je démarre quelque chose, d'abord je m'y prépare, genre si je dois faire un boulot physique dur, d'abord je mets une ceinture de force et je l'enlève quand j'ai fini ; là, on dirait que ces deux proc's font l'inverse,
    Sur ce coup-là j'ai vraiment besoin d'une petite explication de texte, en trois mots, hein, tranquille tranquille...

    Pendant que je teste l'autre version...

    Ce qui me fait penser qu'ils sont marrants, les gens de Lazarus : ils pondent un exemple avec du code qui en l'état ne peut rien faire d'autre que ce qui est codé, c-à-d flooder le mémo. Impossible de le dériver pour essayer de faire un truc de plus, .

  10. #10
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 934
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 934
    Par défaut
    Citation Envoyé par Jipété Voir le message
    1- l'affichage démarre sans cliquer sur Start, bizarre bizarre ;
    Oui bon, il te suffit juste de mettre EnterCriticalsection(CSRunning) à la création de la fiche.

    Citation Envoyé par Jipété Voir le message
    2- si Stop fonctionne, la croix de fermeture génère un SIGTERM.
    Là il faut voir en debug mais puisque j'ai pas Lazarus

    Citation Envoyé par Jipété Voir le message
    - comment est-il possible que le mémo se remplisse sans avoir cliqué sur Start ?
    Voir la première remarque.

    Citation Envoyé par Jipété Voir le message
    EnterCriticalsection(CSRunning);
    LeaveCriticalSection(CSRunning);
    On veut juste savoir quand on peut démarrer mais avec une section critique, pas d'autre choix que de la verrouillé.
    Le but n'est cependant pas de bloquer quoi que ce soit, donc on la libère immédiatement.

    Sinon un appui sur le bouton Stop et la tâche principale est bloquée jusqu'à terminaison de ThreadA. Terminaison qui n'arrivera jamais puisque le thread tourne en boucle. Résultat : Deadlock et bonjour le gestionnaire de tâches !

    Citation Envoyé par Jipété Voir le message
    Ou alors cette entrée dans la critical section c'est bloquant
    C'est ça, un à la fois.
    C'est pas différent de ta partie log.

    Citation Envoyé par Jipété Voir le message
    Pour moi, quand je démarre quelque chose, d'abord je m'y prépare, genre si je dois faire un boulot physique dur, d'abord je mets une ceinture de force et je l'enlève quand j'ai fini ; là, on dirait que ces deux proc's font l'inverse,
    Change le nom de la section critique si c'est plus parlant pour toi
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    procedure TForm1.btnStartClick(Sender: TObject);
    begin
      LeaveCriticalSection(CSPause);
    end;
     
    procedure TForm1.btnStopClick(Sender: TObject);
    begin
      EnterCriticalSection(CSPause);
      Memo1.SetFocus; // juste pour vérifier que le prog n'est pas planté
    end;

  11. #11
    Expert confirmé
    Avatar de Jipété
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    11 132
    Détails du profil
    Informations personnelles :
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 11 132
    Par défaut
    Citation Envoyé par Andnotor Voir le message
    Oui bon, il te suffit juste de mettre EnterCriticalsection(CSRunning) à la création de la fiche.
    Fait.
    Donc la fenêtre s'affiche et reste sagement vide, bien.
    Je ne fais rien d'autre, je la regarde puis je décide de cliquer sur la croix de fermeture et patatras :

    Nom : run-error-236.png
Affichages : 287
Taille : 38,3 Ko

    La ligne concernée c'est celle qu'on voit juste au-dessus de la fenêtre de notification... Je donne ma langue au chat.

    Citation Envoyé par Andnotor Voir le message
    Change le nom de la section critique si c'est plus parlant pour toi
    Oui, je préfère. Merci pour l'idée.
    Dans la foulée, me suis permis de renommer un poil certaines choses (après m'être embrouillé avec A_Counter que je traduisais par un compteur, ), ça donne ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    const
      LM_LOG       = LM_USER;
      thA_WaitForB = 1;
      thB_Counter  = 2;
      thB_Working  = 3;
      thB_Running  = 4;
      thB_WakeA    = 5;
    et tu noteras que ce compteur, je l'ai renommé B puisqu'il bosse dans le thread B, je dirais même plus, thB comme threadB.

    J'ai préféré cet ordre (avoir les variables prêtes, en cas de besoin) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      Wnd := Handle; // inversé avec dessous
      // on s'est croisé les posts -- c'est là que j'ai inséré EnterCriticalsection(csPause);
      ThreadA := TThreadA.Create(False);
      ThreadA.FreeOnTerminate := True; // new -- à moins que ça soit une mauvaise idée ?
    end;
    et j'ai conservé ça pour faire un coucou au passage à l'ami Jurassic Pork :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
     LeaveCriticalSection(CSRunning);
      // jurassicpork :
      if ThreadA <> nil then ThreadA.Terminate;
      //ThreadA.Free; plus besoin avec le FreeOnTerminate dans FormCreate
    end;
    Ça va jusque là ?

    Bon app',

  12. #12
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 934
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 934
    Par défaut
    Citation Envoyé par Jipété Voir le message
    Je ne fais rien d'autre, je la regarde puis je décide de cliquer sur la croix de fermeture et patatras
    C'est toujours un problème lorsqu'on partage des éléments entre plusieurs threads. La section finalization semble être exécutée avant la fin de ThreadA, CSRunning devient invalide et soulève l'exception.

    Ajoute :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      LeaveCriticalSection(CSRunning);
      ThreadA.Terminate;
      ThreadA.WaitFor;
      ThreadA.Free;
    end;
    Citation Envoyé par Jipété Voir le message
    et tu noteras que ce compteur, je l'ai renommé B puisqu'il bosse dans le thread B
    Il est posté depuis A, il était logique de l'appeler A. Je n'ai pas modifié ton code plus que ça.

    Citation Envoyé par Jipété Voir le message
    ThreadA.FreeOnTerminate := True; // new -- à moins que ça soit une mauvaise idée ?
    C'est une mauvaise idée dès que tu gardes une référence sur l'objet puisque la variable n'est pas mise à nil.
    FreeOnTerminate sera utilisé par exemple si tu voulais copier un fichier volumineux en tâche de fond, tu lances la copie et ne t'en occupe plus.

    Citation Envoyé par Jipété Voir le message
    perdu, le plantage en clôture est aléatoire
    Il faut assimiler le concept de multi-tâches, pense comme si c'était deux programmes indépendants. Tu ne sais pas dans quel ordre l'OS a décidé de les planifier et où ils en sont de leur travail. Une fois CSRunning est encore valide, l'autre fois plus. C'est juste une question de timing.

    La synchro ThreadA.WaitFor ci-dessus réglera définitivement le problème.

    Citation Envoyé par Jipété Voir le message
    À quoi servent/correspondent ces "0" et ce "1" en 4e paramètre des PostMessage ?
    C'est LParam qui est utilisé ici que pour le A_Counter. "1" est un copier/coller malheureux une nouvelle fois.

    Citation Envoyé par Jipété Voir le message
    dans btnStartClick, j'ai juste LeaveCriticalSection(csPause); et au début de ThreadB.Execute je lis RtlEventWaitFor(WaitRunning)
    Les deux ne sont pas liés et les concepts sont différents. Les events vont dire à l'OS "ne me planifie plus jusqu'à condition" et les sections critiques comme dit précédemment sont pour le "un à la fois".

  13. #13
    Expert confirmé
    Avatar de Jipété
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    11 132
    Détails du profil
    Informations personnelles :
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 11 132
    Par défaut
    Bon, je ne comprends plus rien à ce qui se passe avec les posts de dvlp qui disparaissent et réapparaissent selon leur bon vouloir : ma réponse de 12 h 35 fait ainsi référence à ton post du 28 où il y a 2 codes et on va dire qu'on s'en fiche sinon on ne s'en sortira pas.

    Donc ici et maintenant je continue sur ton code de 8 h 22 (qui n'était pas visible quand j'ai commencé mes essais et ma réponse) et voilà ce que je peux en dire :

    - concernant WaitFor, en Lazarus je suis obligé de mettre les parenthèses et d'y mettre une valeur dedans sinon ça ne compile pas.
    Premier essai avec que des "0", c'est plantage direct. Alors j'ai mis un "1" partout sauf là :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    for i := 1 to 5 do
       begin
         if Terminated or (WaitRunning.WaitFor(0) = wrTimeout) then Break;
    et ça a bien planté aussi, alors j'ai mis un "1" et je suis arrivé à ça :

    Nom : nouveau-code.jpg
Affichages : 328
Taille : 84,2 Ko

    Si dans l'image on voit mal l'ascenseur à droite c'est parce que j'ai bougé la fenêtre du message et comme l'appli dessous est plantée, elle ne rafraichit plus son ihm.

    Et on voit aussi que l'affichage est loin d'être parfait : le bloc du compteur à 1900 est ok, celui à 1901 a loupé une ligne, et les autres après c'est en vrac jusqu'au SIGABRT...

    Selon la valeur ça plante + ou - vite : 0 instantané, 1 ça va plus loin, 5 encore plus loin.
    Est-ce qu'il faut que je modifie les autres, au pif ? Que dit l'aide de Delphi ?

    - concernant tout le reste, je n'ai touché à rien et il n'y a même plus besoin de rajouter de saut de ligne : tu peux m'expliquer ce tour de magie ?

    C''est tout, bon app',

  14. #14
    Expert confirmé
    Avatar de Jipété
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    11 132
    Détails du profil
    Informations personnelles :
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 11 132
    Par défaut
    Bonjour,

    Un oubli/une erreur dans le premier code, à la première ligne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    var
      CSRunning   : TCriticalSection; // original, ne compile pas !
      CSRunning   : TRTLCriticalSection; // mieux ;-)
    Ensuite, comm' d'hab', F9 et l'activité démarre aussitôt, s'arrête instantanément () au Stop mais SIGTERM à la fermeture du prog...

    L'autre code : même erreur que ci-dessus, et aussi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      ThreadA := TThreadA.Create(False);
      Wnd := Handle;
    end;
    plante grave au démarrage avec une erreur que je n'avais jamais rencontrée, donc je reprends mon option :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      Wnd := Handle; // ça d'abord puisque c'est utilisé dans ThreadA.Execute -- je ne comprends pas que Delphi accepte ta version
      ThreadA := TThreadA.Create(False);
    end;
    Ensuite tout va bien jusqu'au Stop à qui il faut un peu de temps pour s'arrêter, et la croix de fermeture comm' d'hab', SIGTERM au bout de qq secondes...

    Et comme je suis un maniaque, j'ai rajouté mon truc pour le saut de ligne
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
          ...
          PostMessage(Wnd, LM_LOG, A_Counter, ThreadB.Counter);
          PostMessage(Wnd, LM_LOG, B_LineJump, 0); // insère const CRLF = #13#10;
    mais ça me génère deux sauts, moi pas comprendre...

    Nom : essai_30-09.png
Affichages : 255
Taille : 16,9 Ko

    Essais :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    procedure TForm1.LMLog(var Message :TMessage);
    const
    //  CRLF = #13#10; // --> 2 sauts de ligne
    //  CRLF = #10;    // --> 2 sauts de ligne
        CRLF = #13;    // --> 1 seul
    Ça doit être un sac de nœuds quelque part entre Lazarus et Linux et les threads et que sais-je encore, en tout état de cause j'utilise ailleurs cette constante (1re ligne) sans générer de double saut de ligne.

    Autres choses :
    - il me semblait avoir répondu hier en fin de journée ET vers 23 h et ce matin, pas trace de ça... Si ça n'apportait rien, ok, mais pourquoi je ne reçois pas d'email auto de dvlp ?
    - si tu penses en toute honnêteté que ton Delphi ne peut rien faire de plus face à ce problème de + en + typiquement Lazarus dans Linux, alors on laisse tomber.
    EDIT : mais qu'est-ce qui se passe ? les posts "disparus" viennent de réapparaître, c'est hallucinant ! /EDIT

  15. #15
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 934
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 934
    Par défaut
    Si tu lis mes réponses en diagonale on va pas s'en sortir

    Ce sont des millisecondes et si WaitFor n'a pas de paramètre par défaut, tu mets INFINITE !

    Citation Envoyé par Jipété Voir le message
    je ne comprends pas que Delphi accepte ta version
    Je ne vois pas où tu as vu que je l'avais mis après, même si ça n'a pas grande importance où il est placé.

    Le problème est que tu ne réfléchis toujours pas multitâches. Tu crées une instance de TThreadA qui aura pour effet de demander à l'OS de planifier un nouveau thread. Mais d'ici qu'il démarre (qu'on entre dans Execute donc) on aura quitté FormCreate depuis longtemps ! Ca n'a rien d'instantané.

    Ne pas confondre l'instance de TThread qui est juste un banal objet et le thread proprement dit qui y est manipulé.

    Après ce que je te montre là est le concept du multi-threading. Dans un cas réel Wnd devrait être une variable de TThreadA (et TThreadB) assignée par son constructeur. Idem pour les events qui seraient totalement invisibles de l'extérieur de ces classes et manipulés par des méthodes Start/Stop.

    Je ne voulais juste pas ajouter de complexité avec de l'héritage et de l'encapsulation. C'est raté manifestement

  16. #16
    Expert confirmé
    Avatar de Jipété
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    11 132
    Détails du profil
    Informations personnelles :
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 11 132
    Par défaut
    Citation Envoyé par Andnotor Voir le message
    Si tu lis mes réponses en diagonale on va pas s'en sortir
    C'est à cause des posts qui disparaissent et réapparaissent comme par enchantement.

    Citation Envoyé par Andnotor Voir le message
    Je ne vois pas où tu as vu que je l'avais mis après, même si ça n'a pas grande importance où il est placé.
    Là, par exemple :
    Citation Envoyé par Andnotor Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      ThreadA := TThreadA.Create(False);
      Wnd := Handle;
    end;
    Quant à l'importance de sa place, sous Lazarus j'ai eu un plantage violent comme je n'en avais encore jamais vu dans ce projet...

    Citation Envoyé par Andnotor Voir le message
    Ce sont des millisecondes et si WaitFor n'a pas de paramètre par défaut, tu mets INFINITE !
    Ah, enfin une information précise !
    Quant à INFINITE, c'est sans doute le mot qui fait peur, en y ajoutant la notion de deadlock si rien ne vient décoincer l'attente : un TimeOut qui tourne ad vitam æternam n'est pas un timeout, stricto sensu, même si INFINITE n'est pas une valeur infinie,
    Citation Envoyé par lien Andnotor
    The calling thread will wait indefinitely when the constant INFINITE is specified for the TimeOut parameter.
    C'est faux, j'ai fait un test avec ShowMessage(inttostr(INFINITE)); et ça m'a affiché 4 294 967 296, soit 49 jours si j'ai bien converti (je passe ce qui suit la virgule)

    Ceci étant dit, avec du INFINITE partout, ça a tourné comme une horloge suisse, je l'ai arrêté au bout d'une heure, le compteur à 2207.
    Et maintenant je clique sur la croix et... suspense insoutenable... ça s'est bien terminé sans plantage !
    Juste une remarque : un clic sur la croix de fermeture met du temps à réagir (environ 1 seconde -- pas dramatique, juste curieux).

    Un grand, un énorme BRAVO à toi et à ta patience.

    J'attends un jour ou deux pour voir comment je vais gérer l'ajout d'un vrai bouton Pause pour que le bouton Stop fasse un vrai stop avec reset du compteur, pour le moment il fait plutôt Pause et le Start qui suit continue où c'était arrêté...

  17. #17
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 934
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 934
    Par défaut
    Tu chipotes encore !

    Citation Envoyé par Jipété Voir le message
    un TimeOut qui tourne ad vitam æternam n'est pas un timeout, stricto sensu
    WaitFor n'est pas un timeout et un WaitFor qui attend ad vitam æternam n'a pas de timeout.

    Mais c'est sûr qu'on pourrait la jouer shakespearienne : function THandleObject.WaitFor(TimeoutOrNotTimeout: Cardinal): TWaitResult;.


    Citation Envoyé par Jipété Voir le message
    C'est faux, j'ai fait un test avec ShowMessage(inttostr(INFINITE)); et ça m'a affiché 4 294 967 296, soit 49 jours si j'ai bien converti
    Ton raisonnement est faux !

    INFINITE = pas de timeout
    Timeout max = 2 147 483 647 ms

    On passe à la fonction un cardinal mais c'est traité derrière comme un entier signé et une valeur négative différente d'INFINITE est fixée à MAXINT.

    The time-out value needs to be a positive number between 0 and 0x7FFFFFFF. The maximum time-out value not equal to infinity is 0x7FFFFFFF. The infinite time-out value is 0xFFFFFFFF. Any time-out value between 0x7FFFFFFF and 0xFFFFFFFF—that is, values from 0xF0000000 through 0xFFFFFFFE—is equivalent to 0x7FFFFFFF.
    Cela dit si tu crées un thread et tout ce qui va avec pour éventuellement le démarrer tous les N jours, il y a probablement un problème de conception


    Sinon content que tu sois arrivé à tes fins

  18. #18
    Expert confirmé
    Avatar de Jipété
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    11 132
    Détails du profil
    Informations personnelles :
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 11 132
    Par défaut
    Bonjour,

    Citation Envoyé par Andnotor Voir le message
    Tu chipotes encore !
    Oui, et ce n'est pas fini, lire ci-après la tartine (mais comme je ne suis pas là cet aprème, ça laisse du temps pour réfléchir, )

    Citation Envoyé par Andnotor Voir le message
    Sinon content que tu sois arrivé à tes fins
    Tout ça grâce à toi et à ta patience, encore merci, et tu vas voir, ce qui suit n'est pas piqué des vers, j'ai pris des notes ce matin :

    +++++++++++++++++++++
    Avant d'aller plus loin, je fais quelques tests ainsi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      ShowMessage(inttostr(MAXLONGINT)+' - '+inttostr(INFINITE)); // 2147483647 - 4294967295
      if WaitRunning.ManualReset then ShowMessage('WaitRunning.ManualReset is True') else ShowMessage('WaitRunning.ManualReset is False');
      if WaitFinish.ManualReset then ShowMessage('WaitFinish.ManualReset is True') else ShowMessage('WaitFinish.ManualReset is False');
      Application.Terminate; // dessus affiche WaitRunning.ManualReset is True puis WaitFinish.ManualReset is False
      ...
    end;
    La première ligne montre qu'en Lazarus la valeur d'INFINITE n'est pas la même qu'en Delphi : ça ne va pas compliquer les choses, ça ?
    D'ailleurs, je me suis ensuite rendu compte de ça, dans TThreadB.Execute :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
        for i := 1 to 5 do
        begin 
          //if Terminated or (WaitRunning_MRTrue.WaitFor(INFINITE) = wrTimeout) then Break;
          // attend l'expiration du TimeOut - si c'est infini alors c'est Terminated qui fait sortir ?
          // essayé avec une valeur de 3000, empêche le Stop donc INFINITE fait tourner sans fin
          // donc on pourrait s'en passer : je le commente dessus et je mets ça dessous
          if Terminated then Break;
    et ça fonctionne aussi bien. Mais je ne sais pas si je vais le garder, j'attends vos avis.

    Ensuite les events sont définis l'un à True et l'autre à False, dans Initialization, ce qui m'a incité à jouer de la touche F1, et j'ai trouvé ça, dans TEventObject.SetEvent :
    SetEvent sets the event.
    If the ManualReset is True any thread that was waiting for the event to be set (using WaitFor) will resume it's operation. After the event was set, any thread that executes WaitFor will return at once. If ManualReset is False, only one thread will be notified that the event was set, and the event will be immediately reset after that.

    que je trouve un peu lourdingue, pour True.
    Me demande si ça ne serait pas mieux ainsi :
    If ManualReset is True any thread that was waiting for the event to be set (using WaitFor) will resume it's operation when the event will be set and will return.
    c'est plus "light", ça me plait bien !

    Et du coup j'ai renommé ces deux events, l'un comme WaitRunning_MRTrue et l'autre comme WaitFinish_MRFalse. Plus qu'à bien se souvenir, en lisant le code, de If MR True any thread will resume on event et de If MR False 1 thread notified and event reset.

    Mais il n'y a pas un mot sur le reset de l'event en cas de MR à True alors que c'est bien écrit en cas de MR à False : so what ?

    Enfin, je suis passé à 3 boutons (puisque le Stop se comporte comme un Pause), ce qui donne ça, après rajout d'une information complémentaire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    procedure TForm1.btnStartClick(Sender: TObject);
    begin
      WaitRunning_MRTrue.SetEvent;
      PostMessage(Wnd, LM_LOG, AB_Texte, 1);
    end;
     
    procedure TForm1.btnPauseClick(Sender: TObject);
    begin
      WaitRunning_MRTrue.ResetEvent;
      PostMessage(Wnd, LM_LOG, AB_Texte, 2);
    end;
     
    procedure TForm1.btnStopClick(Sender: TObject);
    begin
      WaitRunning_MRTrue.ResetEvent;
      PostMessage(Wnd, LM_LOG, AB_Texte, 3);
      Counter := 0;
      PostMessage(Wnd, LM_LOG, A_Counter, Counter);
    end;
    mais ce Counter dans btnStop oblige à le déplacer du TThreadB vers
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    var
      Form1: TForm1;
      Counter :integer;
    et avec une petite modif dans LMLog :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var
      s: string;
    begin
      case Message.WParam of
        ...
        B_WakeA     : Memo1.Lines.Add('B: Wake A');
        AB_Texte    : begin
          case Message.LParam of
            1: s := 'Started';
            2: s := 'Paused';
            3: s := 'Stopped';
          end;
          Memo1.Lines.Add('Texte : ' + s);
        end;
      end;
    et l'ajout d'une constante,
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    const
      ...
      AB_Texte    = 6;
    j'arrive à ça (les lignes commençant par __ ont été ajoutées après le run) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    __Start cliqué
    Texte : Started
    A: wait for B ...
    B: Working ... ... ... ... ... ...
    B: Wake A
    B: Working ...
    A: ThreadB.Counter=1
     
    A: wait for B ... ... ... ... ... ...
    B: Wake A
    B: Working ...
    A: ThreadB.Counter=2
     
    A: wait for B ... ... ... ... ... __Pause cliqué
    Texte : Paused ...
    B: Wake A
    A: ThreadB.Counter=3
     
    __pause effective ici, puis Start cliqué
    Texte : Started
    B: Working ...
    A: wait for B ... ... ... ... ... ...
    B: Wake A
    B: Working ...
    A: ThreadB.Counter=4
     
    A: wait for B ... ... ... ... ... ...
    B: Wake A
    B: Working ...
    A: ThreadB.Counter=5
     
    A: wait for B ... ...             __Stop cliqué
    Texte : Stopped
    A: ThreadB.Counter=0
     ... ... ... ...
    B: Wake A
    A: ThreadB.Counter=1
     
    __et rend la main ici
    qui me donne envie de creuser du côté de SendMessage plutôt que PostMessage car on voit bien ci-dessus que les réactions aux clics sont un peu dans le désordre : regardez les deux premières lignes avec "A: wait for B" et leurs points de suspension. Mais n'y aura-t-il pas des effets de bord ?

    Jusque là c'est très bien, sans plus aucune erreur depuis hier.

    Bon app', bon aprème, see you later, alligator !

Discussions similaires

  1. Réponses: 6
    Dernier message: 27/07/2017, 03h17
  2. Besoin d'éclaircissement sur les évenements
    Par franquis dans le forum jQuery
    Réponses: 2
    Dernier message: 24/12/2010, 09h55
  3. besoin d'aide sur les thread
    Par demonofshadow dans le forum Windows Forms
    Réponses: 8
    Dernier message: 14/01/2010, 15h07
  4. Besoin d'éclaircissement sur les sockets
    Par Delphy113 dans le forum Langage
    Réponses: 3
    Dernier message: 11/06/2008, 18h10
  5. [CR] besoin d'aide sur les formules
    Par GuillaumeDSA dans le forum Formules
    Réponses: 4
    Dernier message: 10/07/2003, 12h19

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo