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

Multithreading Discussion :

QThread et QCoreApplication : fermeture propre de l'application


Sujet :

Multithreading

  1. #1
    Rédacteur
    Avatar de Erakis
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Octobre 2003
    Messages
    523
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 523
    Points : 233
    Points
    233
    Par défaut QThread et QCoreApplication : fermeture propre de l'application
    Bonjour à tous,

    J'ai débuté Qt il y a de ça 3 semaines. J'ai plutôt un background Windows (C++, C#, Javascript, etc..).

    Je dois réaliser une application qui deviendra par la suite un deamon et/ou un service windows. Toutefois, pour le moment ce qui fonctionne le mieux pour débugger c'est la console évidemment.

    Ce service ou deamon devra communiquer en tout temps avec un port série, donc il doit fonctionner 24h/24h, à moin d'être arrêter manuellement.

    Si j'étais en C++ Windows, je crois que j'implémenterais une classe (disons nommer Worker) et c'est elle qui ferait tout le travail. Donc, elle aurait que 3 fonctions publiques soit Init, Start et Stop. Elle démarrerait un thread qui lorsqu'on appel la fonction Init. Les fonction Start et Stop basculerait le thread soit en mode actif ou pause. Et le plus important c'est que dans son destructeur elle s'assure que le thread est bel et bien ARRÊTER correctement.

    Jusqu'ici j'ai réussi assez facilement à créer le projet, implémenter la classe, démarer le thread et tout...Cependant, pour la question d'arrêter le thread correctement, alors là c'est n'importe quoi

    Sur Windows, une application console peut être fermée de plusieurs façon, dont :
    • Du bouton X de la fenêtre
    • CTRL+C
    • ALT+F4
    • ... et quelques autres façon

    J'ai tenté d'intercepté ces évements avec le mécanisme de QT (QApplicationCore::aboutToQuitApp), mais elle n'est jamais déclanché ! La seul façon que j'ai réussi c'est en interceptant les signal (SIGINT, SIGTERM, SIGBREAK, etc...) :

    Disons que par la suite que j'ai voulu ARRÊTER correctement mon thread, c'est à dire :
    1. Lui indiquer qu'il doit arrêter
    2. Attendre qu'il ait terminé
    3. Et finalement fermer l'application console

    Voici ce que j'ai tenté jusqu'ici : Ne soyez dur avec le code (scope, privé, publique, bool au lieu de QWaitCondition etc...), j'ai fait beaucoup de test et j'ai raccourci beaucoup pour simplifer et que ça soit pas trop long pour mettre 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
    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
     
    #include <QCoreApplication>
    #include <QThread>
    #include <QDebug>
    #include <csignal>
     
    class WorkerThread : public QThread
    {
    public:
        explicit WorkerThread (QObject *parent = 0)
            :m_bStopThread(false) { }
     
    private:
        void run()
        {
            // Le thread ne doit JAMAIS arrêter, à moins qu'on lui 
            // demande explicitement via m_bStopThread
            while (!m_bStopThread)
            {
                qDebug() << "Depuis le worker thread: " << currentThreadId();
                QThread::msleep(1000);
            }
            return; // Ce break-point n'est JAMAIS atteint !
        }
    public :
        volatile bool m_bStopThread;
     
    public Q_SLOTS:
     
        void aboutToQuitApp()
        {
            m_bStopThread = true; // Ce break-point n'est JAMAIS atteint !
        }
    };
     
    struct CleanExit
    {
        static WorkerThread* pWorkerThread;
     
        CleanExit(WorkerThread* _pWorkerThread)
        {
            CleanExit::pWorkerThread = _pWorkerThread;
     
            signal(SIGINT, &CleanExit::exitQt);
            signal(SIGTERM, &CleanExit::exitQt);
            signal(SIGBREAK, &CleanExit::exitQt) ;
        }
     
        static void exitQt(int sig)
        {
            // Une fois le SIGINT intercepté, le debugger se 
            // rend ici, mais il disparait lorsqu'on jump sur 
            // la ligne suivante. Comme s'il plantait (QtCreator et VS 2012) !
            pWorkerThread->m_bStopThread = true;
            pWorkerThread->wait(); 
     
            QCoreApplication::exit(0);
        }
    };
    WorkerThread* CleanExit::pWorkerThread = NULL;
     
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
     
        WorkerThread wk;
        CleanExit cleanExit(&wk);
     
        QObject::connect(&wk, SIGNAL(finished()), &a, SLOT(quit()));
        QObject::connect(&a, SIGNAL(aboutToQuit()), &wk, SLOT(aboutToQuitApp()));
     
        qDebug() <<"Démarrage du worker thread : " << QThread::currentThreadId();
     
        // Démarrage du Thread
        wk.start();
     
        // Attendre que le thread soit bel et bien arrêter !
        wk.wait();
     
        return a.quit();
    }
    Avec les commentaires dans le code vous comprendrez sûrement plus que si j'explique. En gros l'application semble décéder et ammener avec elle le worker thread, sans que j'ai de possibilité de syncroniser cet arrêt correctement.

    Bref, j'ai passé un trop grand nombre d'heure à essayer de faire fonctionner tout cela et je suis découragé. J'espère que quelqu'un ici aura des suggestions ou des solutions.

    Merci à tous.

  2. #2
    Membre averti
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2010
    Messages
    248
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : Suisse

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Mai 2010
    Messages : 248
    Points : 421
    Points
    421
    Par défaut
    Bonjour,

    Tout d'abord, il me paraît étrange que le signal QApplication::aboutToQuit() ne soit pas généré. Es-tu bien sûre que la connexion as été faite correctement ? n'y a-t-il pas de message dans la fenêtre de sortie debug indiquant une mauvaise connexion ?

    Je n'ai pas de solution toute faite, mais par contre je pense qu'il serait préférable de partir sur la méthode du "worker-object", utilisant la fonction QObject::moveToThread(). C'est la méthode recommandée par Qt depuis quelques temps déjà.

    Voir les explication ici :
    Vous vous y prenez mal...
    et ici (en anglais):
    Doc officielle v5.3

    Si l'on reprend le code exemple de la doc officielle:
    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
    class Worker : public QObject
    {
        Q_OBJECT
     
    public slots:
        void doWork(const QString &parameter) {
            QString result;
            /* ... here is the expensive or blocking operation ... */
            emit resultReady(result);
        }
     
    signals:
        void resultReady(const QString &result);
    };
     
    class Controller : public QObject
    {
        Q_OBJECT
        QThread workerThread;
    public:
        Controller() {
            Worker *worker = new Worker;
            worker->moveToThread(&workerThread);
            connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
            connect(this, &Controller::operate, worker, &Worker::doWork);
            connect(worker, &Worker::resultReady, this, &Controller::handleResults);
            workerThread.start();
        }
     
        //Fonction ajoutée pour démarrer le thread
        void start(const QString &parameter){
            emit operate(parameter);
        }
     
        ~Controller() {
            workerThread.quit();
            workerThread.wait();
        }
    public slots:
        void handleResults(const QString &);
    signals:
        void operate(const QString &);
    };
    Cela devrait régler le problème d'attente de fin de vie du thread, étant donnée que le destructeur de la classe Controller attend que le thread se termine avec QThread::wait().

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
     
        // Démarrage du Thread
        Controller controller;    
        controller.start("Parameter String");
     
        return a.quit();
    }
    Je n'ai pas testé le code. Et je dois avouer que les threads ce n'est pas mon fort donc je ne garantie pas que cela soit la bonne solution.

    D'ailleurs il y a une autre solution, peut-être plus simple, qui est d'utilisé la fonction QtConcurrent::run() qui permet de lancer une fonction dans un thread séparé, et d'attendre que le traitement se termine en utilisant un objet QFuture.

    Exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
     
        // Démarrage du Thread
        QFuture<void> future = QtConcurrent::run(myFunction /*, parameters*/);
        //Attente 
        future.waitForFinished();
     
        return a.exec();
    }
    Comme je l'ai dit je n'ai pas de solution toute faite. Mais j’espère ceci pourra t'aider à résoudre ton problème.

  3. #3
    Rédacteur
    Avatar de Erakis
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Octobre 2003
    Messages
    523
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 523
    Points : 233
    Points
    233
    Par défaut
    Bonjour Gojir4,

    Tout d'abord un GROS merci pour votre soutiens, c'est apprécié. L'aide se fait rare sur ce sujet (Qt + Thread + Console).

    J'ai essayé quelque truc en me basant sur ce que vous avez proposé. En passant, merci pour les liens, toutefois je les avais déjà lu malgré mon pétrin actuel.

    Voici ce que ça me donne :
    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
    class Worker : public QObject
    {
        Q_OBJECT
     
    public:
        Worker()
            : bStopThread(false) {
        }
     
    public slots:
        void doWork(const QString &parameter) {
            QString result;
            while (bStopThread == false) {
                qDebug() << "Thread operation\n";
                QThread::msleep(500);
            }
            emit resultReady(result);
        }
     
    signals:
        void resultReady(const QString &result);
     
    public:
        bool bStopThread;
    };
     
    class Controller : public QObject
    {
        Q_OBJECT
        QThread workerThread;
        Worker *worker;
    public:
        Controller() {
            worker = new Worker;
            worker->moveToThread(&workerThread);
            connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
            connect(this, &Controller::operate, worker, &Worker::doWork);
            connect(worker, &Worker::resultReady, this, &Controller::handleResults);
            workerThread.start();
        }
     
        //Fonction ajoutée pour démarrer le thread
        void start(const QString &parameter){
            emit operate(parameter);
        }
     
        void stop(const QString &parameter){
            worker->bStopThread = true;
        }
     
        ~Controller() {
            workerThread.quit();
            workerThread.wait();
        }
    public slots:
        void handleResults(const QString & result)
        {
            QString s = result;
            s = s;
        }
    signals:
        void operate(const QString &);
    };
     
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
     
     
        // Démarrage du Thread
        Controller controller;
        controller.start("Parameter String");
     
        //return a.quit();
        return a.exec();
    }
    Cependant, j'ai toujours le même problème, c'est à dire que lorsque je clique sur le X de la fenêtre de la console, TOUT s'arrête. Aucun desstructeur, slot, événemement, rien Esssayez-le vous verrai...

    La plupart des exemple que l'on trouve sur le web sont des exemples qui démarre une ou plusieurs thread et une fois le travail de ceux-ci terminer (thread.join) ils arrêtent l'application manuellement (a.quit).

    Moi j'ai besion d'un thread qui fonctionne TOUJOURS. Et c'est le thread de l'application qui lui dira QUAND arrêter et ensuite l'application quittera d'elle même. Et quand je dit arrêter, c'est que l'application pourra appeler un méthode STOP sur le thread qui elle fera le ménage correctement avant tout destructeur ou fermeture du programme.

    Enfin, je vais voir si je pourrais pas communiquer avec QT.

    Merci

    J'ai remarqué aussi que dans votre exemple, vous utilisez la méthode (QUIT) du QThread.

  4. #4
    Membre averti
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2010
    Messages
    248
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : Suisse

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Mai 2010
    Messages : 248
    Points : 421
    Points
    421
    Par défaut
    La plupart des exemple que l'on trouve sur le web sont des exemples qui démarre une ou plusieurs thread et une fois le travail de ceux-ci terminer (thread.join) ils arrêtent l'application manuellement (a.quit).

    Moi j'ai besion d'un thread qui fonctionne TOUJOURS. Et c'est le thread de l'application qui lui dira QUAND arrêter et ensuite l'application quittera d'elle même. Et quand je dit arrêter, c'est que l'application pourra appeler un méthode STOP sur le thread qui elle fera le ménage correctement avant tout destructeur ou fermeture du programme.
    Effectivement dans ce cas, il faut indiquer à l'objet Worker qu'il doit stopper la boucle. Mais c'est bien pour cela que la fonction Controller::stop() existe n'est-ce pas? Il manque l'appel de stop() dans le destructeur de la classe Controller.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    ~Controller() {
            stop();
            workerThread.quit();
            workerThread.wait();
        }
    J'ai remarqué aussi que dans votre exemple, vous utilisez la méthode (QUIT) du QThread.
    En effet il est conseillé d'utiliser QThread::quit() plutôt que QThread::terminate(). Voici un extrait de la doc de QThread::terminate() sur ce site:

    Attention : cette fonction est dangereuse et son utilisation est déconseillée. Le thread peut être terminé à n'importe quel point de son code. Il pourrait être terminé pendant la modification de données. Il n'y a pas moyen que le thread nettoie après son passage, débloque le moindre mutex, etc. En bref, n'utilisez cette fonction que si cela est réellement nécessaire.
    En résumé, utiliser terminate() c'est un peu comme tuer un processus depuis le gestionnaire des tâches sous Windows .

  5. #5
    Rédacteur
    Avatar de Erakis
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Octobre 2003
    Messages
    523
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 523
    Points : 233
    Points
    233
    Par défaut
    Vous avez raison, j'ai omis l'appel à la fonction "stop" dans le destructeur de la classe du Controller, merci. Cependant, comme je le mentionnais, le destructeur n'est JAMAIS appelée d'aucune façon alors cela ne change malheureusement rien au problème.

    J'ai un background quotodiens en programmation C++ Win32/MFC sous Windows [Desktop /Windows CE] et tout cela pour vous dire que je n'ai JAMAIS eu de problème à implémenter ce genre de système.

    Pour le "terminate" versus le "quit" du thread je connais les récupercusions. C'est la même chose que d'appeler TerminateThread sous Windows, ce qui est dûement non conseillé. Et actuellement ce que QT fait lorsqu'on clique sur le "X" de la console ou encore "CTRL+C" nous mènes exactement au comportement que l'on veut éviter avec le TerminateThread. C'est-à-dire que soit l'application ou l'OS arrête inopinément le thread d'une façon anormal. En d'autre mot, on n'a aucune façon d'arrêter ce thread et d'attendre qu'il soit arrêter avant que l'application console se ferme. Et ce, peut importe la façon qu'on s'y prend ou les object QT qu'on utilise.

    C'est à se demander si je suis le seul à vouloir faire une application console ou deamon qui contient un thread ?

  6. #6
    Membre averti
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2010
    Messages
    248
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : Suisse

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Mai 2010
    Messages : 248
    Points : 421
    Points
    421
    Par défaut
    Cependant, j'ai toujours le même problème, c'est à dire que lorsque je clique sur le X de la fenêtre de la console, TOUT s'arrête. Aucun desstructeur, slot, événemement, rien Esssayez-le vous verrai...
    Toutes mes excuses, je viens de comprendre le réel problème. Des fois je lis un peu trop vite. Je n'avais pas fait attention au fait c'était
    le X de la fenêtre de la console
    Effectivement dans ce cas c'est un problème. Le fait de fermer la console va tout simplement tuer le processus sans appeler aucun destructeurs. Il faut trouver un moyen d'envoyer un signal à l'exécutable afin qu'il puisse savoir qu'il doit quitter.

    Par exemple en utilisant un QSystemTrayIcon, malheureusement je crois que cela ne fonctionne pas avec toutes les plateformes. Mais c'est à vérifier dans la doc.

    Sous Windows, cela peut être une bonne solution, et assez discrète et élégante je trouve (mais c'est mon avis). Après il faut voir la compatibilité avec d'autre plateforme.

    Voici un exemple:
    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
     
    #include <QApplication>
    #include <QSystemTrayIcon>
    #include <QMessageBox>
    #include <QAction>
    #include <QMenu>
    #include "controller.h"
     
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
     
        // Démarrage du Thread
        Controller controller;
        controller.start("Parameter String");
     
        if (!QSystemTrayIcon::isSystemTrayAvailable()) {
            QMessageBox::critical(0, QObject::tr("Systray"),
                                  QObject::tr("I couldn't detect any system tray "
                                              "on this system."));
            return 1;
        }
     
        QSystemTrayIcon trayIcon;
        QMenu trayIconMenu;
        QAction quit(QObject::tr("Quit"), &a);
        trayIconMenu.addAction(&quit);
        QObject::connect(&quit, SIGNAL(triggered()), qApp, SLOT(quit()));
        trayIcon.setContextMenu(&trayIconMenu);
        trayIcon.show();
     
        //return a.quit();
        return a.exec();
    }
    La classe Controller est exactement la même que celle de ton poste précédent, excepté l'ajout de stop() dans le desctructeur et quelque message de debug. J'ai simplement mis tout ça dans des fichiers séparés.

    Je n'ai pas défini d'icône pour l'objet QSystemTrayIcon, il faut donc un peu le deviner dans la barre des tâche.
    Nom : QSystemTrayIcon.png
Affichages : 1323
Taille : 6,1 Ko

    Voila ce que j'obtiens en sortie:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Controller::constructor
    Worker::constructor
    Controller::start
    QSystemTrayIcon::setVisible: No Icon set
    Worker::doWork
    Thread operation
     
    Thread operation
    ... 
    Thread operation
    Controller::destructor
    Controller::stop
    Sinon il doit aussi être possible d'intercepter les caractère entrés dans la console, et par exemple, quitter lorsque un mot clef est détecté.

    Une troisième solution pourrait être de faire un second soft, avec un interface graphique, qui permet de gérer le processus console.

    C'est à se demander si je suis le seul à vouloir faire une application console ou deamon qui contient un thread ?
    En fait c'est bien possible! Je ne pense pas que ça soit très courant mais ce n'est qu'une supposition
    Quel sera le but final de l'application ?

Discussions similaires

  1. Fermeture propre des tables
    Par marcha dans le forum Requêtes
    Réponses: 11
    Dernier message: 16/06/2009, 16h27
  2. fermeture aumatique de votre application
    Par Invité dans le forum Contribuez
    Réponses: 2
    Dernier message: 16/04/2007, 10h01
  3. Fermeture inopinée de l'application
    Par slimjoe dans le forum Delphi
    Réponses: 7
    Dernier message: 06/09/2006, 11h25
  4. Réponses: 5
    Dernier message: 28/04/2006, 18h45

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