Partage
  • Partager sur Facebook
  • Partager sur Twitter

ESC pour interrompre un calcul

Application WinForm C#

    8 juin 2020 à 16:55:22

    Bonjour à tous,

    J'ai construit une application Winform en C#.
    En cliquant sur un bouton, une procédure avec de nombreux calculs est lancée.
    J'aimerais pouvoir arrêter correctement la procédure en appuyant sur la touche d'échappement (ESC).

    Mais il y a plusieurs problèmes :

    L'événement KeyDown mis en place sur le formulaire principal ne peut pas être remontée (comme une interruption) pendant que la procédure est en cours.
    L'événement KeyDown ne peut pas être détecté tant que l'appelant (fonction button_Click()) n'a pas rendu le focus sur le formulaire principal.
    L'événement KeyDown semble n'être détectable que lorsque l'application est disponible, en attente.

    J'ai essayé différentes solutions :

    J'ai placé la procédure de calcul dans un thread séparé, lancé par un bouton dont la fonction button_Click() se termine immédiatement après.
    De cette façon, la détection de l'événement KeyDown devient possible au niveau du Thread principal (Form1). Ainsi, à l'aide d'un Flag, il est possible d'arrêter la procédure placée dans le fil.
    Le problème est que les autres tâches qui devaient suivre la fin des calculs doivent également être placées dans le thread, sinon il n'est pas possible de continuer les opérations dans le thread principal après la fin des calculs.

    Autre test : Utiliser un "Low level Keyboard hook" (https://youtu.be/f5gLsA7wX5U).
    Il fonctionne bien du moment qu'il utilisé dans le thread principal (Form1) et que l'application est en attente d'entrées de l'utilisateur, mais si le fil principal est en train d'exécuter la procédure de calcul (ce qui est le but), l'événement n'est détecté qu'à la fin du calcul, ce que l'on ne veut évidemment pas.
    J'ai donc pensé à placer cette capture de clavier de bas niveau dans un autre thread séparé. Mais je ne sais pas comment faire.

    Je cherche donc une chose très simple (cliquer sur ESC pour arrêter un calcul), mais il me semble difficile d'y arriver !
    Y a-t-il quelqu'un pour m'aider, s'il vous plaît ?
    Merci beaucoup.

    Emmanuel
    • Partager sur Facebook
    • Partager sur Twitter
      8 juin 2020 à 18:43:54

      quelques DoEvents suivi du test du flag répartis dans la fonction de calcul, et ça devrait faire l'affaire
      • Partager sur Facebook
      • Partager sur Twitter
        9 juin 2020 à 2:02:42

        Quel DoEvents ?

        Faut arrêter ces cochonneries tout droit sortie du VB6 des années 90.

        Le "Keyboard Hook", c'est pas fait pour ça.

        La méthode la plus simple, c'est l'utilisation de méthode asynchrones (await/async).

        https://docs.microsoft.com/fr-fr/dotnet/csharp/programming-guide/concepts/async/

        Il existe aussi la solution d'un thread de travail, comme vous le proposez comme première solution (ou l'usage du pool de threads avec les Task).

        Je ne comprends pas trop votre problème avec cette solution.

        Les choses à faire après le calcul, si cela n'a rien à voir avec l'IHM, vous n'avez qu'à les appeler en fin de la méthode exécutée par le thread.

        S'il y a des synchronisations à faire avec l'IHM, il y a Invoke ou des classes spécialisées :

        https://docs.microsoft.com/fr-fr/dotnet/api/system.windows.forms.control.invoke?view=netcore-3.1

        https://www.codeproject.com/Tips/83317/BackgroundWorker-and-ProgressBar-demo

        • Partager sur Facebook
        • Partager sur Twitter
        Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
          9 juin 2020 à 17:58:47

          Bonjour et un grand merci pour votre retour !!

          Tout d'abord, j'imaginais qu'il y avait une solution simple à mon problème ... quelque chose qui m'échappait ... mais bon.
          Je suis OK pour tous les éléments que vous m'avez communiqués.

          Besoin : Fonctionnellement, l'utilisateur clique sur un bouton pour lancer un calcul long (quelques minutes). En cliquant sur ESC, il peut interrompre le calcul et continuer à utiliser l'application, voire relancer les calculs.

          Problématique : Comme je ne peux faire ces deux choses (calculs et détection des touches) dans le même thread (celui du formulaire de base), je déporte les calculs dans un thread séparé lancé par l'appui sur le fameux bouton. Mais pour détecter la touche ESC, il faut qu'après avoir lancé le Thread de calculs, la fonction de lancement se termine et rende la main à l'application (thread principal). Or en faisant cela, c'est toute l'application qui est rendue à l'utilisateur qui peut alors cliquer ailleurs, alors que le thread de calcul est toujours en cours ... ce que je ne souhaite pas. Je préférerai que le clic sur le bouton soit "bloquant" pour l'utilisateur, qui doit attendre la fin des calculs (quelques minutes) avant toute autre opération sur l'application, sauf à cliquer sur ESC qui viendrait arrêter les calculs.

          Voilà pourquoi je me tourne vers vous :)

          • Partager sur Facebook
          • Partager sur Twitter
            9 juin 2020 à 20:05:18

            >j'imaginais qu'il y avait une solution simple à mon problème

            Cela peut être très simple, si on prépare correctement le travail.

            Si votre algorithme long est facilement "découpable" en petites étapes, vous pouvez très bien faire le taf sans vos prendre la tête avec le moindre thread, juste en utilisant des appels de méthode asynchrones.

            Vous ne "pouvez/devez" pas interrompre une tâche de manière complètement arbitraire. Vous devez "signaler" votre tâche pour qu'elle se termine en bon ordre. En vérifiant un flag à chaque étape de son calcul, une tâche longue pourra s'interrompre ELLE-MEME en bon ordre.

            Il faut donc un canal de communication entre la tâche et l'environnement, une variable "flag" fera l'affaire.

            https://stackoverflow.com/questions/3632149/question-about-terminating-a-thread-cleanly-in-net

            Vous faites des raisonnements faux pour cause de manque de connaissances.

            >Comme je ne peux faire ces deux choses...

            Bin si, il faut juste utiliser les bons outils. (on a des machines avec plusieurs cœurs, c'est pas fait pour les chiens, et des ordonnanceurs à la ms, c'est pas fait pour les chiens non plus).

            >Mais pour détecter la touche ESC

            La détection des touches ne se fait que dans le thread ayant créé la fenêtre ayant le focus clavier.

            En fonction des mécanismes utilisée pour lancer le traitement long, la gestion de cette touche peut se résumer à l'appel d'une méthode (Interface d'appel asynchrones : https://docs.microsoft.com/fr-fr/dotnet/standard/parallel-programming/task-cancellation ), ou l'utilisation d'un canal de communication avec la tâche longue et attendre son feedback, etc...

            Les mécanismes asynchrones à utiliser sont fonction du type de traitement (interactif avec l'IHM, calculs parallélisables, etc...).

            J'ai l'impression que votre "conception" de l'IHM lors de l'exécution de la tâche n'est qu'une simple messageBox Modal.

            Normalement, en mode Modal, une boite de dialogue câble automatiquement la touche "ESC" avec le bouton "Annuler", que la fonction appelante pourra facilement récupérer et gérer juste en appelant le mécanisme d'annulation de la tâche qui a été lancé juste avant l'appel à ShowDialog

            https://docs.microsoft.com/fr-fr/dotnet/api/system.windows.forms.form.showdialog?view=netcore-3.1

            Pour moi, c'est juste la création d'une Task, appel à ShowDialog d'un Winform avec juste un bouton d'annulation, d’appeler "Cancel" de la Task si l'appel à ShowDialog de la fenêtre n'est pas sortie en OK, et faire en sorte que la fenêtre de dialogue sorte en OK quand la tâche longue à terminer avec succès (avec des Invoke ou autre).

            • Partager sur Facebook
            • Partager sur Twitter
            Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
              11 juin 2020 à 11:08:54

              Bonjour,

              Merci beaucoup "bacelar" pour cette longue et intéressante réponse.

              Voici quelques éléments complémentaires et retour sur vos avis :

              • Appel de méthodes asynchrones : je vais étudier comment l'appliquer à mon besoin, mais il me semble que c'est ce que j'ai fait en lançant le thread de calcul via le bouton de l'IHM sans attendre sa fin d'exécution. Or en faisant cela, obligatoire pour pouvoir détecter la touche, l'utilisateur peut alors par exemple appuyer sur un autre bouton de l'IHM alors que les calculs sont encore en cours ... pas terrible, car fonctionnellement, cela pourrait surprendre l'utilisateur.
              • Interrompre une tâche / thread proprement : C'est bien ce que j'ai fait, via un flag positionné selon l'événement touche. Elle / il se termine proprement en faisant le ménage des données et variables.
              • Merci pour la confirmation quant au "lieu" de détection des événements clavier. Donc si je veux que le thread principal détecte la touche, il faut qu'il soit "libre" pour cela.
              • MessageBox Modal : non pas du tout. Imaginez une IHM (Form1 principal) avec N boutons et plein d'autres choses, dont un bouton permettant de lancer des calculs et dont les résultats s'affichent après calcul dans la même IHM. Donc rien de modal, ni de gestionnaire de touche ESC par défaut.
              Bref, l'idée d'un thread séparé chargé de détecter la touche ESC tombe à l'eau, ce qui est bien dommage, car il pourrait via le positionnement de Flag, influer sur le thread principal. Etes-vous bien d'accord avec cela ?
              Je vais donc garder l'idée du thread séparé pour les calculs + mise à jour de l'IHM via Invoke().
              Ce qui me gène, c'est comment interdire à l'utilisateur d'interagir avec l'IHM tant que le thread de calcul ne l'a pas autorisé !! Il faudrait positionner un flag derrière tous les boutons (lourds et non propre) piloté par le thread de calculs. Peut-être y a-t'il une méthode plus simple ?
              Merci à vous. 
              • Partager sur Facebook
              • Partager sur Twitter
                11 juin 2020 à 14:18:33

                >mais il me semble que c'est ce que j'ai fait en lançant le thread de calcul

                Non, multi-threading et appel asynchrone ne sont pas la même chose.

                Les appel asynchrones peuvent utiliser des threads, mais ce n'est pas obligatoire.

                Vous n'avez pas compris l'usage d'un affichage modal d'un formulaire.

                Vous avez une application "standard" et le bouton de lancement est dans cette partie.

                Lors du lancement du calcul, vous affichez en mode modal un formulaire qui résume les interactions que vous autorisez pendant ce calcul.

                La fenêtre modale est exécutée par le thread principal, qui peut réagir aux entrées claviers, mais qui verrouille tous les éléments d'interface autres que ceux affichés dans la fenêtre modale : c'est bien ce que vous cherchez à faire, non ?

                >Bref, l'idée d'un thread séparé ...

                Pourquoi ne pas faire ce positionnement dans le thread principal ? (Qui est toujours "libre" dans un "showdialog")

                MessageBox est un cas d'affichage modal d'une fenêtre mais n'importe quel formulaire peut être affiché en "modal".

                • Partager sur Facebook
                • Partager sur Twitter
                Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.

                ESC pour interrompre un calcul

                × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                • Editeur
                • Markdown