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

 C Discussion :

Entrez "quit" pour quitter ce : Comment faire ça sans bloquer dans la boucle ?


Sujet :

C

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Homme Profil pro
    Responsable de compte
    Inscrit en
    Juin 2014
    Messages
    219
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 56
    Localisation : France, Pyrénées Orientales (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Responsable de compte

    Informations forums :
    Inscription : Juin 2014
    Messages : 219
    Par défaut Entrez "quit" pour quitter ce : Comment faire ça sans bloquer dans la boucle ?
    Bonjour.

    J'ai un programme qui tourne pour un nombre d'itérations données.

    Je souhaiterais pouvoir l'arrêter avant d'atteindre la dernière itération en entrant "q" ou "quit" au clavier.

    Je précise que le programme n'attends pas et n'a pas besoin d'une entrée clavier d'une donnée quelconque de la part de l'utilisateur pour itérer.

    Le programme fait son travail, et quand je décide de l'arrêter prématurément, je rentre "q" au clavier, c'est tout !

    Or, tout les exemples que je trouve sont basés sur un code qui demande une réponse de l’utilisateur pour pouvoir itérer ! Le code bloque en attendant une réponse. Or je ne veux pas ça !

    Exemple de code essayé qui ne correspond pas !
    Source du code ici
    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
    #include <stdio.h>
    int main()
    {
        char ch;
        puts("Typing Program");
        puts("Type away; press 'q' to quit:");
        for(;;)
        {
            ch=getchar();
            if(ch=='~')
            {
                break;
            }
        }
        return(0);
    }
    Impossible de trouver ce que je souhaite !

  2. #2
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Il n'y a que deux solutions:
    demander à l'utilisateur d'interrompre le programme lui-même (avec CTRL+C sous linux, ou l'équivalent de windows).
    utiliser un thread supplémentaire.

    Dans l'idée du thread, il faut initialiser une variable dédiée (par exemple finDemandee) avant d'itérer, et vérifier si sa valeur a changé à chaque itération.
    Dans un thread, on fait une boucle, donc bloquant ce thread uniquement, pour demander q ou quit. Lorsque la réponse est satisfaisante, on modifie finDemandee puis on termine le thread.

    Ceci provoquera la fin du programme lorsque la boucle principale passera à l'itération suivante.

    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
     
    void attendre_quitter(int * terminer) {
        puts("Typing Program");
        puts("Type away; press 'q' to quit:");
        while (getchar()!='~);
        *terminer = 1;
    }
     
    int main() {
        int terminer = 0;
        //du code pour appeler "attendre_quitter(&terminer)" dans un autre thread;
        while(!terminer) {
            calculer_un_peu_plus()
        }
        return 0;
    }

  3. #3
    Membre Expert
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Par défaut
    Tu veux de l'interactivité en ligne de commande : il n'existe pas de solution portable à ton problème sans recourir à des bibliothèques tierces.

    Trois axes de réflexion :

    • passer en mode non canonique pour la durée d'exécution du programme, avec toutes les conséquences que cela implique sur le comportement et la portabilité du programme ;
    • exploiter un système qui va exécuter le traitement et intercepter les évènements de manière asynchrone, comme par exemple une bibliothèque évènementielle telle libuv (overkill ici, AMHA) ;
    • prendre en charge les signaux et plus particulièrement SIGTERM que l'utilisateur peut envoyer via Ctrl-C .

    Dans le cas que tu évoques, ce dernier point a ma préférence personnelle et est sans doute la best practice, au moins sur les systèmes Posix-compliant. C'est plus générique et ça évite au programme de faire de l'entrée-sortie qui le détourne de sa tâche principale.

  4. #4
    Membre confirmé
    Homme Profil pro
    Responsable de compte
    Inscrit en
    Juin 2014
    Messages
    219
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 56
    Localisation : France, Pyrénées Orientales (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Responsable de compte

    Informations forums :
    Inscription : Juin 2014
    Messages : 219
    Par défaut
    Gloups !
    Je suis débutant !

    La solution CTRL + C ne me convient pas car, en fait, je souhaiterais avoir plusieurs options pour quitter le programme.

    La solution de ternel semblait accessible, à part :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    //du code pour appeler "attendre_quitter(&terminer)" dans un autre thread;
    Sinon, je me fais la main sur une Raspberry pi, mais après, ce serait pour faire tourner sur une machine windows. Comme vous faisiez référence à la notion de portabilité .....

  5. #5
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Et bien, tu en as de la chance

    le Raspberry PI utilise généralement un linux, donc l'API posix, tandis que Windows fait à sa sauce.
    Conclusion, utiliser les apis manuellement n'est pas portable.
    Tu peux t'en sortir avec les threads de C11. (si c'est supporté sur le PI)

    Cela dit, je confirme et conseille la réponse de Matt_Houston: intercepte le sigterm, et en guise de signal, utilise une fonction qui modifie la variable de controle.
    C'est sensiblement équivalent au thread, mais sans thread, et c'est portable.

  6. #6
    Membre confirmé
    Homme Profil pro
    Responsable de compte
    Inscrit en
    Juin 2014
    Messages
    219
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 56
    Localisation : France, Pyrénées Orientales (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Responsable de compte

    Informations forums :
    Inscription : Juin 2014
    Messages : 219
    Par défaut
    Comme je l'ai dit plus haut, je débute en programmation !
    J'ai l'impression que ça fait beaucoup de notion d'un coup pour moi !

  7. #7
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 476
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 476
    Par défaut
    Citation Envoyé par hary66 Voir le message
    Comme je l'ai dit plus haut, je débute en programmation !
    J'ai l'impression que ça fait beaucoup de notion d'un coup pour moi !
    Pour faire court :

    • getch(), c'est spécifique Windows. Enfin, ça l'est quand tu utilises conio.h, parce que ncurses a décidé de nommer un de ses appels de la même façon et de proposer en plus des services qui correspondraient aussi à ce que tu cherches, mais il y a 99 % de chances que ton programme ne s'appuie pas dessus si tu ne lui as pas demandé explicitement de le faire. Si tu débutes en C et que tu veux lire un caractère, utilise getchar() qui, lui, fait partie de la norme C ;
    • En C (ainsi qu'avec pas mal d'autres langages ET systèmes d'exploitation ayant suivi le même modèle), lorsque tu fais une opération de lecture sans préciser la source, celle-ci est par défaut « l'entrée standard », et cette entrée standard est elle-même, par défaut, reliée au clavier de la console de l'utilisateur qui a lancé ton programme MAIS ce n'est pas une obligation : cela peut être la sortie d'un autre programme si l'utilisateur a utilisé un pipe « | » ou une connexion à distance par modem. Il se peut même que cela ne soit rien du tout ;
    • Ton programme gère l'entrée standard comme s'il s'agissait d'un fichier et il attend sagement que le prochain caractère arrive si le système ne lui répond pas carrément qu'il en a atteint la fin. Avec un clavier, on n'atteint jamais cette fin puisque qu'il est toujours possible de saisir une touche après la précédente ;
    • Si tu prends l'exemple de ton Raspberry Pi, celui-ci est fait pour fonctionner tout seul par défaut : il n'y a ni écran ni clavier connecté dessus. Dans ces conditions, ton programme ne pourrait pas être exécuté s'il dépendait directement d'un clavier.


    Ceci veut dire que gérer le clavier efficacement relève de la programmation système : il faut demander à ton OS d'aller paramétrer la gestion d'un périphérique en particulier, en l'occurrence le clavier, ou LES claviers car aujourd'hui, non seulement il y a plusieurs périphériques de saisie, mais même un truc aussi élémentaire que le clavier peut provenir de sources différentes : port PS/2 ou USB, par exemple. Donc, généralement, tous ces périphériques sont réunis en une seule classe et il y a une couche d'abstraction au-dessus pour faire croire qu'il s'agit d'une et unique source. C'est à ce niveau-là qu'il faut intervenir.

    Donc, concrètement, les approches qui s'offrent à toi aujourd'hui, sont :

    • Utiliser kbhit() (littéralement « frappe clavier ») qui est un appel non-bloquant et qui t'informe qu'on a appuyé sur le clavier. Donc, un caractère (hors touches spéciales) a bien été saisi et tu pourras le récupérer avec getchar() ou getch() sans risque de blocage MAIS c'est là encore spécifique à Windows, et non seulement ça ne fonctionnera que sur les machines qui ont un clavier, mais cela t'empêchera de chaîner ton programme avec « | » (il continuerait à attendre bêtement le clavier alors que les caractères proviendraient d'une autre source). Donc à oublier ;
    • Soit tu informes le système que tu lis bien l'entrée standard (quelque soit la provenance des caractères à lire) mais que tu ne souhaites pas que les appels soient bloquants, ce qui est en fait exactement l'énoncé de ta demande initiale : donc tu fais un appel en entrée de programme pour modifier les paramètres d'un flux de fichier (ici l'entrée standard) avec fcntl() et tu lui passes le flag O_NONBLOCK. À noter que ceci provient des systèmes Unix et donc aujourd'hui de la norme POSIX, reconnue par Windows, mais pas de la norme C. C'est donc bien de la programmation système ;
    • Soit encore, s'appuyer sur les systèmes de signaux comme proposé plus haut pour faire complètement abstraction de la gestion de l'entrée utilisateur et laisser les mécanismes systèmes traditionnels prendre cela en charge. Tu peux mettre en place un « gestionnaire de signal » avec signal() ou sigaction() pour qu'une certaine procédure soit automatiquement exécutée à réception dudit signal. Tu peux te servir de ce handler pour changer l'état d'un flag qui serait lu depuis ton programme principal, qui lui proposerait le menu de sortie s'il voit qu'il a changé, à la manière de finDemandee proposé par ternel. C'est propre, mais la tentation d'utiliser une variable globale pour implémenter ce flag est grande, et c'est là encore une pratique que l'on essaie de faire éviter aux débutants. Par ailleurs, il faut bien faire attention au type de signal reçu et à l'instance qui les a envoyés. Si c'est l'utilisateur, ça ne pose pas de problème mais si c'est le système qui l'a fait parce que l'ordinateur est en train de s'éteindre, par exemple, alors il vaut mieux quitter silencieusement et ne pas interroger l'utilisateur (qui ne serait plus là pour répondre) ;
    • Soit utiliser poll() ou select() pour vérifier si un ou plusieurs sont bloquants à un moment donné. C'est LA voie à suivre lorsque l'on veut suivre plusieurs flux à la fois, par exemple, si l'on gère un serveur ;
    • Soit décider d'utiliser ncurses et d'invoquer ses propres appels pour gérer cela à ta place (mais ça ne fait que déplacer le problème) ;
    • Soit construire toute ton application sur une bibliothèque d'usage général et de plus haut niveau, comme la SDL.


    kbhit() est vraisemblablement ce qui va te tirer d'affaire pour ce cas précis, mais considère que tu n'auras plus jamais à y recourir par la suite. Si c'est quand même le cas, c'est que ton programme est mal conçu.

    Sinon, select et poll sont intéressants même pour surveiller un seul flux car il permettent de spécifier une valeur de timeout qui peut éventuellement être nulle (donc rendre la main immédiatement) et parce que cela permet de faire des opérations sans avoir à changer explicitement les flags du flux concerné et d'avoir à les remettre en place ensuite.

    Sache enfin que sous Unix et assimilés, les applications « consoles » sont en fait exploitées à travers des « terminaux virtuels » (la console proprement dite ou les xterm de X-Window). Ces terminaux fonctionnent en mode canonique par défaut, ce qui signifie qu'ils sont faits pour bufferiser la ligne que saisit l'utilisateur et lui permettre de la corriger, jusqu'à ce qu'il appuie sur Entrée, moment où le contenu du buffer est envoyé au processus concerné. Ça veut dire que tu ne recevrais les caractères qu'après avoir tapé Entrée. Si tu veux avoir une réception immédiate, il faudra se pencher sur termios où, là encore, laisser ncurses gérer tout cela pour toi.

  8. #8
    Expert confirmé
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    4 062
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 4 062
    Par défaut
    Citation Envoyé par hary66 Voir le message
    Comme je l'ai dit plus haut, je débute en programmation !
    J'ai l'impression que ça fait beaucoup de notion d'un coup pour moi !
    Ouais mais nous on va pas faire le taf pour toi, alors comment on fait pour résoudre cette problématique ?

  9. #9
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 476
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 476
    Par défaut
    Bonjour,

    Citation Envoyé par ternel Voir le message
    Il n'y a que deux solutions:
    demander à l'utilisateur d'interrompre le programme lui-même (avec CTRL+C sous linux, ou l'équivalent de windows).
    utiliser un thread supplémentaire.
    Euh… non ! On ne peut pas te laisser dire cela. Certes, utiliser un thread est une pratique courante — aujourd'hui — quand ceux-ci sont disponibles et que l'on utilise une interface utilisateur sophistiquée, ce qui lui permet d'être réactive même si des travaux asynchrones fonctionnent en arrière-plan. Mais il faut être capable de le faire sur toutes sortes de machines, surtout s'il s'agit simplement de saisir l'état d'une touche pour sortir. Si ce n'était pas possible sans thread, c'est l'informatique grand public entière qui n'aurait démarré qu'au milieu des années 2000.

    Citation Envoyé par hary66 Voir le message
    La solution CTRL + C ne me convient pas car, en fait, je souhaiterais avoir plusieurs options pour quitter le programme.

    La solution de ternel semblait accessible, à part :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    //du code pour appeler "attendre_quitter(&terminer)" dans un autre thread;
    Sinon, je me fais la main sur une Raspberry pi, mais après, ce serait pour faire tourner sur une machine windows. Comme vous faisiez référence à la notion de portabilité .....
    Dans ce cas-là, n'utilise pas « getch() » sans savoir exactement à quelle interface tu fais appel. L'appel POSIX est déprécié s'il n'a pas d'underscore « _ » initial. Ensuite, cet appel ne fait PAS partie du C standard. Il est défini soit par conio.h qui est spécifique à Windows, soit par curses ou ncurses qui est une bibliothèque de plus haut niveau et qui prend en charge la gestion de la saisie.

    Si tu t'appuies sur ncurses, tu peux tout de suite passer en mode non bloquant avec nodelay(). L'appel à getch() renvoie alors ERR s'il n'y a pas de caractère disponible. Extrait de la man page concernée :

    Citation Envoyé par man getch
    DESCRIPTION
    Reading characters
    The getch, wgetch, mvgetch and mvwgetch, routines read a character from the window. In no-delay mode, if no input is waiting, the value ERR is returned. In delay mode, the
    program waits until the system passes text through to the program.
    Mais sinon, en s'appuyant au moins sur C et sur POSIX uniquement, il faut soit passer l'entrée standard en mode non-bloquant grâce à fcntl() puis laisser getchar() (et pas « getch() ») renvoyer EOF, soit passer par select() pour monitorer un ensemble de flux.

    Fais une recherche avec select() sur ce forum car le sujet a été maintes fois disserté ici, et parce que ça permet d'éviter de faire une attente active consommant le temps processeur pour rien si jamais il n'y a rien à traiter.

Discussions similaires

  1. Déclaration de variable pour un template -> comment faire ?
    Par souffle56 dans le forum XSL/XSLT/XPATH
    Réponses: 14
    Dernier message: 16/03/2010, 23h27

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