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 :

Arrêter, mettre en pause et recommencer [QThread]


Sujet :

Multithreading

  1. #1
    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 Arrêter, mettre en pause et recommencer
    Bonjour à tous,

    Je débute actuellement dans l'utilisation des threads avec Qt (et en général).
    J'ai donc mis en place un petit projet de test en m'inspirant des divers recommandations de la doc Qt et des forums.

    J'essaye d'utiliser la solution utilisant QObject::moveToThread() plutôt que d'hériter de QThread. Cette dernière solution ne permettant pas d'utiliser les signaux/slots .

    J'ai donc implémenté une class "Worker" dont le rôle sera de traiter des données. J'ai ajouter deux slots cancel() et togglePause(), qui je connect directement au signal clicked() des mes boutons (ui->m_btStop et ui->m_btPause).
    Le problème étant que ces deux slots ne sont jamais appelé, et je ne comprend pas pourquoi. Si quelqu'un peux m'éclairer, ca serait vraiment sympa.

    Voici mon code :
    worker.h
    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
    #ifndef WORKER_H
    #define WORKER_H
     
    #include <QObject>
    #include <QMutex>
    #include <QWaitCondition>
     
    #include "data.h"
     
    class Worker : public QObject
    {
        Q_OBJECT
    public:
        explicit Worker(QObject *parent = 0);
     
    signals:
        void maxProgressRangeChanged(int maxRange);
        void progressValueChanged(int value);
        void progressTextChanged(const QString &text);
        void finished();
     
        void resultReady(const Data &result);
     
    public slots:
     
        void cancel();
        void togglePaused();
     
        void startWork(const QStringList &list);
     
    public:
        bool isPaused();
        bool isRunning();
     
    private:
        QMutex m_sync;
        QWaitCondition m_pauseCond;
        QWaitCondition m_1secCond;
        volatile bool m_pause;
        volatile bool m_stop;
        bool m_isRunning;
     
    };
     
    #endif // WORKER_H
    worker.cpp
    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
    #include <QStringList>
    #include <QTime>
     
    #include "worker.h"
     
    Worker::Worker(QObject *parent) :
        QObject(parent)
    {
        m_pause = false;
        m_stop = false;
        m_isRunning = false;
    }
     
    //Never called - Why ? 
    void Worker::cancel()
    {
        m_sync.lock();
        m_stop = true;
    	m_sync.unlock();
    }
     
    //Never called - Why ? 
    void Worker::togglePaused()
    {
        m_sync.lock();
        m_pause = !m_pause;
        if(!m_pause) m_pauseCond.wakeAll();
        m_sync.unlock();
     
    }
     
    void Worker::startWork(const QStringList &list)
    {
     
        m_sync.lock();
        m_pause = false;
        m_stop = false;
        m_isRunning = true;
        m_sync.unlock();
        int counter = 0;
     
        QStringList internalList = list;
     
        emit maxProgressRangeChanged(list.size());
        emit progressValueChanged(0);
        emit progressTextChanged(tr("Starting...."));
     
        foreach(QString element, internalList){
            //Check pause state
            //=================
            m_sync.lock();
            if(m_pause)
                m_pauseCond.wait(&m_sync); // in this place, your thread will stop to execute until someone calls resume
            if(m_stop) {
                m_isRunning = false;
                m_sync.unlock();
                emit finished();
                break;
            }
            m_sync.unlock();
     
            //Data process
            //=================
            emit progressValueChanged(++counter);
            emit progressTextChanged(tr("Processing \"%1\"").arg(element));
     
            Data data(element.toUpper(), counter);
            //Wait 200 ms to make the process long (this is a test),
            //this can simulate a slow communication, waiting on a hardware, etc..
    		QTime t;
    		t.start();
            while(t.elapsed() < 200){}
     
            //send the data result
            emit resultReady(data);
        }
        m_sync.lock();
        m_pause = false;
        m_stop = false;
        m_isRunning = false;
        m_sync.unlock();
        emit finished();
    }
     
    bool Worker::isPaused()
    {
        m_sync.lock();
        bool res = m_pause;
        m_sync.unlock();
        return res;
     
    }
     
    bool Worker::isRunning()
    {
        m_sync.lock();
        bool res = m_isRunning;
        m_sync.unlock();
        return res;
    }
    MainWindow.h
    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
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
     
    #include <QMainWindow>
     
    #include "data.h"
     
    class Worker;
    class QThread;
     
     
    namespace Ui {
    class MainWindow;
    }
     
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
     
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
     
    public slots:
        void onBtStart();
        void onBtPause();
        void onBtStop();
     
        void dataReady(const Data &data);
        void onProcessFinished();
     
        void threadStarted();
        void threadFinished();
     
    signals:
        void startWork(const QStringList &list);
     
        void pause();
        void stop();
     
    private:
        Ui::MainWindow *ui;
        Worker *worker;
        QThread *thread;
    };
     
    #endif // MAINWINDOW_H
    MainWindow.cpp
    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
    #include <QThread>
     
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
     
    #include "worker.h"
     
     
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
     
        qRegisterMetaType<Data>();
     
        worker = new Worker;
        thread = new QThread(this);
     
        ui->progressBar->setVisible(false);
        ui->laProcess->setVisible(false);
        ui->btPause->setEnabled(false);
        ui->btStop->setEnabled(false);
        ui->btStart->setEnabled(true);
     
        connect(worker, SIGNAL(maxProgressRangeChanged(int)), ui->progressBar, SLOT(setMaximum(int)));
        connect(worker, SIGNAL(progressTextChanged(QString)), ui->laProcess, SLOT(setText(QString)));
        connect(worker, SIGNAL(progressValueChanged(int)), ui->progressBar, SLOT(setValue(int)));
        connect(worker, SIGNAL(resultReady(Data)), this, SLOT(dataReady(Data)));
        connect(worker, SIGNAL(finished()), this, SLOT(onProcessFinished()));
     
        connect(thread, SIGNAL(started()), this, SLOT(threadStarted()));
        connect(thread, SIGNAL(finished()), this, SLOT(threadFinished()));
     
        connect(ui->btPause, SIGNAL(clicked()), worker, SLOT(togglePaused()), Qt::QueuedConnection);
        connect(ui->btStop, SIGNAL(clicked()), worker, SLOT(cancel()), Qt::QueuedConnection);
        connect(ui->btStart, SIGNAL(clicked()), this, SLOT(onBtStart()));
     
        connect(this, SIGNAL(startWork(QStringList)), worker, SLOT(startWork(QStringList)));
     
        worker->moveToThread(thread);
    }
     
    MainWindow::~MainWindow()
    {	
        emit stop();
        thread->exit();
        thread->wait();
        delete thread;
     
        delete ui;
    }
     
    void MainWindow::onBtStart()
    {
       QStringList list;
     
       thread->start();
     
       ui->progressBar->setVisible(true);
       ui->laProcess->setVisible(true);
     
       //Set random data 
       list << "Nullam" << "libero" << "at" << "sapien" << "vestibulum";
     
       //Start processing of the data
       emit startWork(list);
       ui->btPause->setEnabled(true);
       ui->btStop->setEnabled(true);
       ui->btStart->setEnabled(false);
    }
     
    void MainWindow::onBtPause()
    {
        emit pause();
    }
     
    void MainWindow::onBtStop()
    {
        emit stop();
    }
     
    void MainWindow::dataReady(const Data &data)
    {
        Data internalData(data);
        ui->edLog->append(QString("Data processed : %1 --> %2")
                          .arg(internalData.value(), 8, 10, QChar('0'))
                          .arg(internalData.name()));
    }
     
    void MainWindow::onProcessFinished()
    {
        ui->edLog->append("All data has been processed ! ");
        ui->progressBar->setVisible(false);
        ui->laProcess->setVisible(false);
        ui->btPause->setEnabled(false);
        ui->btStop->setEnabled(false);
        ui->btStart->setEnabled(true);
    }
     
    void MainWindow::threadStarted()
    {
        ui->edLog->append("Thread started ! ");
    }
     
    void MainWindow::threadFinished()
    {
        ui->edLog->append("Thread finished ! ");
    }
    Je n'ai pas ajouté l'image de la GUI, mais c'est très simple,
    - 3 boutons start, pause, stop.
    - 1 progressBar
    - 1 label (traitement en cours)
    - 1 TextEdit pour afficher le log de ce qui se passe.

    Je sais qu'il existe pas mal de topic sur le sujet, mais je n'ai rien trouvé répondant à mon problème ( ou peut être n'ai-je pas compris tout simplement), de plus, ceci étant ma première utilisation des thread avec Qt, j'aimerais vraiment être sûre d'avoir bien compris le sujet pour partir sur de bonne bases.

    Merci d'avance

  2. #2
    Responsable 2D/3D/Jeux


    Avatar de LittleWhite
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2008
    Messages
    26 860
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

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

    Informations forums :
    Inscription : Mai 2008
    Messages : 26 860
    Points : 219 062
    Points
    219 062
    Billets dans le blog
    120
    Par défaut
    Bonjour,

    J'ai un peu de mal à comprendre pourquoi Worker n'hérite pas de QThread ? Cela ne simplifierai pas la tache ?
    Vous souhaitez participer à la rubrique 2D/3D/Jeux ? Contactez-moi

    Ma page sur DVP
    Mon Portfolio

    Qui connaît l'erreur, connaît la solution.

  3. #3
    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
    Citation Envoyé par LittleWhite Voir le message
    Bonjour,

    J'ai un peu de mal à comprendre pourquoi Worker n'hérite pas de QThread ? Cela ne simplifierai pas la tache ?
    Bonjour,

    En fait d'après ce que j'ai compris dans la doc Qt et les forums, si j'hérite de QThread, seul la fonction run() sera exécutée dans un thread différent, et visiblement les signaux et slots ne fonctionneront pas. Dans tous les cas la doc de Qt indique clairement qu'il est déconseillé de définir des nouveau slots dans une classe héritée de QThread.

    En utilisant moveToThread(), l'objet complet est déplacé dans un autre thread. Cette la méthode recommandé par la doc de Qt.

    Une description plus détaillé est disponible ici

    J'éspère avoir répondu à votre question

  4. #4
    Responsable 2D/3D/Jeux


    Avatar de LittleWhite
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2008
    Messages
    26 860
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

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

    Informations forums :
    Inscription : Mai 2008
    Messages : 26 860
    Points : 219 062
    Points
    219 062
    Billets dans le blog
    120
    Par défaut
    Quelque chose me dit que c'est lié au Qt::QueuedConnection. Mais je ne crois pas que ce soit tout, mais le laisser par défault mais un paramètre automatique, qui résoud généralement bien les connexions. Ensuite, il faut voir sur la console ne retourne pas des warnings sur les connexions.

    (Et oui, je m'étais basé sur le modèle 4.X pour ma réponse précédente).
    Vous souhaitez participer à la rubrique 2D/3D/Jeux ? Contactez-moi

    Ma page sur DVP
    Mon Portfolio

    Qui connaît l'erreur, connaît la solution.

  5. #5
    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 QueuedConnection
    Citation Envoyé par LittleWhite Voir le message
    Quelque chose me dit que c'est lié au Qt::QueuedConnection. Mais je ne crois pas que ce soit tout, mais le laisser par défault mais un paramètre automatique, qui résoud généralement bien les connexions. Ensuite, il faut voir sur la console ne retourne pas des warnings sur les connexions.

    (Et oui, je m'étais basé sur le modèle 4.X pour ma réponse précédente).
    En fait j'ai essayé les deux possibilités, en forçant Qt::QueuedConnection ou non (valeur par défaut), mais sans succès dans un cas comme dans l'autre. Désolé j'ai oublié de mentionné cela et de supprimer les "Qt::QueuedConnection" dans mon code.

    Ce que je ne comprend pas c'est que les signaux provenant de worker
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    connect(worker, SIGNAL(resultReady(Data)), this, SLOT(dataReady(Data)));
        connect(worker, SIGNAL(finished()), this, SLOT(onProcessFinished()));
    fonctionnent très bien, alors que dans l'autre
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    connect(ui->btPause, SIGNAL(clicked()), worker, SLOT(togglePaused()));
        connect(ui->btStop, SIGNAL(clicked()), worker, SLOT(cancel()));
    sens cela ne fonctionne pas.

    Edit : Concernant les warnings, je n'en ai aucun s'affichant dans la console.

  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 processEvents
    Je crois que je commence à comprendre. En modifiant l'appel de mes signaux ( j'ai passé par un slot transitoire dans MainWindow, j'avais donc le signal clicked() de mes boutons qui appelait un slot dans MainWindow, qui lui appelait le slot dans worker). A ce moment la j'ai remarqué que mes slots Worker::togglePause() et Worker::cancel() était bien appelé cette fois, mais seulement à la fin du traitement.

    J'ai ajouté
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    QCoreApplication::processEvents();
    à la fin du traitement de ma fonction
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    Worker::startWork(QStringList list){
            ...
            //send the data result
            emit resultReady(data);
     
    		//Process event in the queue
    		QCoreApplication::processEvents();
        }
    et à ce moment la mes slots Worker::cancel() et Worker::togglePause() sont bien appelés au bon moment. Mais j'ai encore dû modifier ma condition d'attente dans la fonction Worker::startWork afin que la ligne m_pauseCond.wait(&m_sync) ne soit pas blockante, sinon c'est le deadlock assuré. La modifs :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    if(m_pause){ // in this place, your thread will stop to execute until someone calls resume
         while(!m_pauseCond.wait(&m_sync, 50)) 
                QCoreApplication::processEvents();
    }
    Cette solution ne me paraît pas idéale , la boucle while va certainement utiliser beaucoup de charge CPU inutilement. Néanmoins je ne vois pas d'autre solutions pour l'instant.

    Merci LittleWhite pour vos réponses rapides ainsi que votre aide qui m'as permis de m'aiguiller et me poser les bonnes questions.

  7. #7
    Rédacteur
    Avatar de Amnell
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2009
    Messages
    1 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 840
    Points : 5 545
    Points
    5 545
    Par défaut
    Bonsoir,

    Du coup, vous partez sur un héritage de QThread ? Le principe des QueuedConnections est que lorsqu'un signal est envoyé à un thread, il va être ajouté à la "file d'attente" des événements à traiter par la boucle d'événements du thread. De ce fait, si votre thread ne fait jamais de pause dans son traitement, il ne va pas permettre le fait que les événements en attente soient traités. Vous paliez à ce problème avec un appel à QCoreApplication::processEvents(). L'alternative serait de placer des pauses dans votre boucle afin de laisser la possibilité à vos événements d'être traités (cela permettra également de relâcher un minimum la pression sur le processeur associé au thread).

    Bonne continuation,
    Amnell.
    N'oubliez pas de consulter la FAQ Qt ainsi que les cours et tutoriels C++/Qt !

    Dernier article : Débuter avec les Enlightenment Foundation Libraries (EFL)
    Dernières traductions : Introduction à Qt Quick - Applications modernes avec Qt et QML
    Vous cherchez un livre sur Qt 5, Qt Quick et QML ? Créer des applications avec Qt 5 - Les essentiels

  8. #8
    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
    Citation Envoyé par Amnell Voir le message
    Bonsoir,

    Du coup, vous partez sur un héritage de QThread ? Le principe des QueuedConnections est que lorsqu'un signal est envoyé à un thread, il va être ajouté à la "file d'attente" des événements à traiter par la boucle d'événements du thread. De ce fait, si votre thread ne fait jamais de pause dans son traitement, il ne va pas permettre le fait que les événements en attente soient traités. Vous paliez à ce problème avec un appel à QCoreApplication::processEvents(). L'alternative serait de placer des pauses dans votre boucle afin de laisser la possibilité à vos événements d'être traités (cela permettra également de relâcher un minimum la pression sur le processeur associé au thread).

    Bonne continuation,
    Amnell.
    Bonjour Amnell,

    Effectivement c'est ce que j'ai compris par la suite. Par contre quand vous dites "placer des pauses", à quoi pensez-vous ? Et quel serait l'avantage par rapport à l'utilisation de QCoreApplication::processEvents() ?

    Le but du thread n'est-il pas justement de pouvoir exécuter une tâche lourde de ce type ? Néanmoins, pour l'application final, j'aurai pas mal de temps d'attente qui pourront être utilisé pour redonne la main à la boucle d'événement.

    Merci pour ces précisions.

  9. #9
    Rédacteur
    Avatar de Amnell
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2009
    Messages
    1 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 840
    Points : 5 545
    Points
    5 545
    Par défaut
    Bonjour,

    Dans votre thread, vous faites :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    while(t.elapsed() < 200){}
    De ce fait, vous bloquez la boucle d'événements du thread tant que la boucle n'est pas arrivée au bout. C'est ici que vous devrez positionner des pauses (msleep de QThread, par exemple) ou bien appeler QCoreApplication::processEvents().

    QCoreApplication::processEvents() est en gros une fonction qui va forcer l'application à traiter les événements en attente (par exemple, un clic sur un bouton, ou autre, par exemple vos signaux). En théorie, si votre application ne prend pas 100% du process pour un thread, cela va laisser la possibilité aux événements d'être traités, donc vous n'aurez pas besoin d'appeler cette fonction manuellement.

    Bonne continuation,
    Amnell.
    N'oubliez pas de consulter la FAQ Qt ainsi que les cours et tutoriels C++/Qt !

    Dernier article : Débuter avec les Enlightenment Foundation Libraries (EFL)
    Dernières traductions : Introduction à Qt Quick - Applications modernes avec Qt et QML
    Vous cherchez un livre sur Qt 5, Qt Quick et QML ? Créer des applications avec Qt 5 - Les essentiels

  10. #10
    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 Ma solution
    La solution de mon dernier post ne fonctionne pas complétement, en effet le thread est bien mis en pause, mais l'on ne ressort jamais de la boucle
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    while(!m_pausCond.wait(&m_sync, 50)){...}
    En effet dans la doc de QWaitCondition, il est indiqué que la fonction wakeAll() doit être appelé depuis un autre thread pour que cela fonctionne.

    J'ai résolu ce problème en englobant ma classe Worker dans une autre classe WorkerHandler (je sais mes noms de classes ne sont pas très recherchés ). Vous pouvez notez dans le code ci-dessous que je passe une référence sur mon objet QWaitCondition dans la fonction WorkerHandler::startWork() qui va être utilisé par la suite pour "réveiller" le thread en mode pause. En faisant cela, l'appel de QWaitCondition::wakeAll() sera bien fait du thread principal et non du thread workerThread.


    Voici ma solution complète :

    Worker.h :
    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
    #ifndef WORKER_H
    #define WORKER_H
     
    #include <QObject>
    #include <QMutex>
    #include <QWaitCondition>
    #include <QDebug>
     
    #include "data.h"
     
    class Worker : public QObject
    {
        Q_OBJECT
    public:
        explicit Worker(QObject *parent = 0);
     
    signals:
        void maxProgressRangeChanged(int maxRange);
        void progressValueChanged(int value);
        void progressTextChanged(const QString &text);
        void finished();
        void resultReady(const Data &result);
     
    public slots:
        void cancel();    
        void pause();
        void setPauseCondition(QWaitCondition &pauseCondition);
        void startWork(const QStringList &list);
     
    private:
        QMutex m_sync;
        QWaitCondition *m_pauseCond;
        volatile bool m_pause;
        volatile bool m_stop;
    };
     
    #endif // WORKER_H
    Worker.cpp :
    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
    #include <QStringList>
    #include <QTime>
    #include <QCoreApplication>
    #include <QThread>
     
    #include "worker.h"
     
    Worker::Worker(QObject *parent) :
        QObject(parent)
    {
        m_pause = false;
        m_stop = false; 
        m_pauseCond = 0;   
    }
     
    void Worker::cancel()
    {
        m_sync.lock();
        m_stop = true;
    	m_sync.unlock();
    }
     
    void Worker::pause()
    {
        m_sync.lock();
        m_pause = !m_pause;
        m_sync.unlock();
        qDebug() << "Pause toggled to " << (m_pause ? "true" : "false" )<< " from thread : " << QThread::currentThreadId();
    }
     
    void Worker::setPauseCondition(QWaitCondition &pauseCondition)
    {
        if(&pauseCondition != 0){
            m_pauseCond = &pauseCondition;
            qDebug() << "Pause condition set from thread : " << QThread::currentThreadId();
        }
    }
     
    void Worker::startWork(const QStringList &list)
    {
        m_sync.lock();
        m_pause = false;
        m_stop = false;    
        m_sync.unlock();
        int counter = 0;
     
        QStringList internalList = list;
     
        emit maxProgressRangeChanged(list.size());
        emit progressValueChanged(0);
        emit progressTextChanged(tr("Starting...."));
        qDebug() << "Start process from thread : " << QThread::currentThreadId();
     
        foreach(QString element, internalList){
            //Check pausing or canceling
            //==========================
            m_sync.lock();
    	if(m_pause && m_pauseCond){
                qDebug() << "Waiting on pause condition from thread : " << QThread::currentThreadId();
                m_pauseCond->wait(&m_sync);
                //Reset pause flag
    	    m_pause = false;
    	}
            if(m_stop) {
                m_sync.unlock();
                //Leave the loop
                break;
            }
            m_sync.unlock();
     
    	//Data process
            //=================
            emit progressValueChanged(++counter);
            emit progressTextChanged(tr("Processing \"%1\"").arg(element));
     
            Data data(element.toUpper(), counter);
            //Wait 200 ms to make the process long (this is a test),
            //this can simulate a slow communication, waiting on a hardware, etc..
    	QTime t;
    	t.start();
            while(t.elapsed() < 200){}
     
            //send the data result
            emit resultReady(data);
     
    	//Process event in the queue
    	QCoreApplication::processEvents();
        }
        m_sync.lock();
        m_pause = false;
        m_stop = false;
        m_sync.unlock();
        emit finished();
    }
    WorkerHandler.h :
    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
    #ifndef WORKERHANDLER_H
    #define WORKERHANDLER_H
     
    #include <QObject>
    #include <QThread>
    #include <QWaitCondition>
     
    #include "worker.h"
     
    class WorkerHandler : public QObject
    {
        Q_OBJECT
    public:
        explicit WorkerHandler(QObject *parent = 0);
    	~WorkerHandler();
     
    signals:
     
        void maxProgressRangeChanged(int maxRange);
        void progressValueChanged(int value);
        void progressTextChanged(const QString &text);
        void finished();
     
        void resultReady(const Data &result);
     
        //Internal signals
        void __pause();
        void __cancel();
        void __startWork(const QStringList &list);
     
    public slots:
        void cancel();
        void togglePaused();
     
        void startWork(const QStringList &list);
     
    public:
        bool isPaused();
        bool isRunning();
     
    protected slots:
        void onFinish();
     
    private:
     
        QWaitCondition m_pauseCond;
        bool m_pause;
        bool m_isRunning;
     
        Worker *worker;
        QThread *workerThread;    
    };
     
    #endif // WORKERHANDLER_H
    WorkerHandler.cpp :
    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
     
    #include "workerhandler.h"
     
     
    WorkerHandler::WorkerHandler(QObject *parent) :
        QObject(parent)
    {
        m_pause = false;
    }
     
    WorkerHandler::~WorkerHandler()
    {
     
    }
    void WorkerHandler::cancel()
    {
        if(m_pause) m_pauseCond.wakeAll();
        emit __cancel();
    }
     
    void WorkerHandler::togglePaused()
    {
        m_pause = !m_pause;
        if(!m_pause) 
    		m_pauseCond.wakeAll();
        else 
    		emit __pause();
    }
     
    void WorkerHandler::startWork(const QStringList &list)
    {
        //Create objects and do connections
        worker = new Worker();
        workerThread = new QThread(this);
        connect(worker, SIGNAL(maxProgressRangeChanged(int)), this, SIGNAL(maxProgressRangeChanged(int)));
        connect(worker, SIGNAL(progressTextChanged(QString)), this, SIGNAL(progressTextChanged(QString)));
        connect(worker, SIGNAL(progressValueChanged(int)), this, SIGNAL(progressValueChanged(int)));
        connect(worker, SIGNAL(finished()), this, SIGNAL(finished()));
        connect(worker, SIGNAL(finished()), this, SLOT(onFinish()));
        connect(worker, SIGNAL(resultReady(Data)), this, SIGNAL(resultReady(Data)));
     
        connect(this, SIGNAL(__cancel()), worker, SLOT(cancel()));
        connect(this, SIGNAL(__pause()), worker, SLOT(pause()));
        connect(this, SIGNAL(__startWork(QStringList)), worker, SLOT(startWork(QStringList)));
     
        connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
        connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
     
        worker->setPauseCondition(m_pauseCond);
        worker->moveToThread(workerThread);
        //Start thread eventloop
        workerThread->start();
     
        //Start the process of the list.
        m_isRunning = true;
        emit __startWork(list);
    }
     
    bool WorkerHandler::isPaused()
    {
        return m_pause;
    }
     
    bool WorkerHandler::isRunning()
    {
        return m_isRunning;
    }
     
    void WorkerHandler::onFinish()
    {
        m_isRunning = false;
        /*
        Exit the thread event loop as we don't need it until new process has to be done.
        The thread is not deleted, as it is done automatically thanks to connection
        of signal finished() to slot deleteLater().
        */
        workerThread->exit();
        workerThread->wait();
    }
    MainWindow.h :
    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
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
     
    #include <QMainWindow>
     
    #include "data.h"
    class WorkerHandler;
     
    namespace Ui {
    class MainWindow;
    }
     
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
     
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
     
    public slots:
        void onBtStart();
     
        void dataReady(const Data &data);
        void onProcessFinished();
     
    signals:
        void startWork(const QStringList &list);
        void pause();
        void stop();
     
    private:
        Ui::MainWindow *ui;    
        WorkerHandler *worker;
    };
     
    #endif // MAINWINDOW_H
    MainWindow.cpp :
    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
    #include <QThread>
    #include <QDebug>
     
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
     
    #include "workerhandler.h"
     
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
     
        qRegisterMetaType<Data>();
     
        worker = new WorkerHandler(this);
     
        ui->progressBar->setVisible(false);
        ui->laProcess->setVisible(false);
        ui->btPause->setEnabled(false);
        ui->btStop->setEnabled(false);
        ui->btStart->setEnabled(true);
     
        connect(worker, SIGNAL(maxProgressRangeChanged(int)), ui->progressBar, SLOT(setMaximum(int)));
        connect(worker, SIGNAL(progressTextChanged(QString)), ui->laProcess, SLOT(setText(QString)));
        connect(worker, SIGNAL(progressValueChanged(int)), ui->progressBar, SLOT(setValue(int)));
        connect(worker, SIGNAL(resultReady(Data)), this, SLOT(dataReady(Data)));
        connect(worker, SIGNAL(finished()), this, SLOT(onProcessFinished()));
        connect(ui->btPause, SIGNAL(clicked()), worker, SLOT(togglePaused()));
        connect(ui->btStop, SIGNAL(clicked()), worker, SLOT(cancel()));
        connect(ui->btStart, SIGNAL(clicked()), this, SLOT(onBtStart()));
        connect(this, SIGNAL(startWork(QStringList)), worker, SLOT(startWork(QStringList)));    
    }
     
    MainWindow::~MainWindow()
    {
        delete ui;
    }
     
    void MainWindow::onBtStart()
    {
       QStringList list;
     
       ui->progressBar->setVisible(true);
       ui->laProcess->setVisible(true);
     
       //Set random data
       list << "Nullam" << "venenatis" << "dui" << "ut" << "purus" << "vulputate" << "congue" << "Fusce" << "lacinia" << "orci"
            << "quis" << "ligula" << "viverra" << "convallis" << "Ut" << "vitae" << "arcu" << "pretium" << "sem" << "mollis"
            << "tristique" << "vitae" << "quis" << "eros" << "Curabitur" << "et" << "dolor" << "ut" << "arcu" << "dictum"
            << "iaculis" << "Pellentesque" << "habitant" << "morbi" << "tristique" << "senectus" << "et" << "netus"
            << "et" << "malesuada" << "fames" << "ac" << "turpis" << "egestas" << "Suspendisse" << "porttitor" << "lacinia"
            << "diam" << "nec" << "imperdiet" << "dui" << "vehicula" << "ultrices" << "Lorem" << "ipsum" << "dolor" << "sit"
            << "amet" << "consectetur" << "adipiscing" << "elit" << "Quisque" << "sed" << "tincidunt" << "tortor" << "Maecenas"
            << "posuere" << "ultrices" << "ultrices" << "Fusce" << "tincidunt" << "libero" << "non" << "diam" << "pellentesque"
            << "id" << "tristique" << "lectus" << "sollicitudin" << "Integer" << "id" << "tortor" << "in" << "metus" << "mollis"
            << "gravida" << "in" << "a" << "ante" << "Mauris" << "convallis" << "tortor" << "et" << "massa" << "vestibulum" << "posuere"
            << "Class" << "aptent" << "taciti" << "sociosqu" << "ad" << "litora" << "torquent" << "per" << "conubia" << "nostra"
            << "per" << "inceptos" << "himenaeos" << "Maecenas" << "elementum" << "blandit" << "quam" << "at" << "convallis"
            << "magna" << "egestas" << "et" << "Phasellus" << "dapibus" << "suscipit" << "ante" << "id" << "blandit" << "velit"
            << "malesuada" << "id" << "In" << "venenatis" << "libero" << "at" << "sapien" << "vestibulum" << "lobortis";
     
       //Start processing of the data
     
       worker->startWork(list);
       ui->btPause->setEnabled(true);
       ui->btStop->setEnabled(true);
       ui->btStart->setEnabled(false);
    }
     
    void MainWindow::dataReady(const Data &data)
    {
        Data internalData(data);
        ui->edLog->append(QString("Data processed : %1 --> %2")
                          .arg(internalData.value(), 8, 10, QChar('0'))
                          .arg(internalData.name()));
    }
     
    void MainWindow::onProcessFinished()
    {
        ui->edLog->append("All data has been processed ! ");
        ui->progressBar->setVisible(false);
        ui->laProcess->setVisible(false);
        ui->btPause->setEnabled(false);
        ui->btStop->setEnabled(false);
        ui->btStart->setEnabled(true);
    }
    Cela me semble être une solution qui suit bien la façon d'utiliser les thread avec Qt, je suis bien entendu ouvert au critiques et suggestions d'améliorations.

    Edit : Merci à LittleWhite et Amnell qui ont étés très réactif pour m'aider sur le sujet.

  11. #11
    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
    Citation Envoyé par Amnell Voir le message
    Bonjour,

    Dans votre thread, vous faites :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    while(t.elapsed() < 200){}
    De ce fait, vous bloquez la boucle d'événements du thread tant que la boucle n'est pas arrivée au bout. C'est ici que vous devrez positionner des pauses (msleep de QThread, par exemple) ou bien appeler QCoreApplication::processEvents().

    QCoreApplication::processEvents() est en gros une fonction qui va forcer l'application à traiter les événements en attente (par exemple, un clic sur un bouton, ou autre, par exemple vos signaux). En théorie, si votre application ne prend pas 100% du process pour un thread, cela va laisser la possibilité aux événements d'être traités, donc vous n'aurez pas besoin d'appeler cette fonction manuellement.

    Bonne continuation,
    Amnell.
    En effet placer le QCoreApplication::processEvents() ou msleep dans cette boucle rendrait la gestion des événements plus réactive, néanmoins j'ai justement besoin de placer mon traitement dans un thread car j'aurai des moment de blocage dans mon application finale (communication série, attente de positionnement d'un moteur, etc) car je travaille avec des fonctions synchrones. C'est pourquoi j'ai rajouter le commentaire
    Wait 200 ms to make the process long (this is a test). This can simulate a slow communication, waiting on a hardware, etc.
    sur cette boucle justement.

    Pour répondre à la question de votre premier poste, je n'ai pas utilisé l'héritage de QThread. J'utilise l'héritage de QObject, puis je place ma classe dans une thread en utilisant
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    QObject::moveToThread(QThread *thread)
    . En effet la méthode qui consistait à hériter de QThread est maintenant considérée comme "mauvaise pratique" par la documentation de Qt

    J'ai entre-temps poster ma solution pour ce problème si cela vous intéresse. N'hésitez pas à me faire des commentaires et suggestions.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Mettre une pause dans un programme
    Par PNL dans le forum Général Java
    Réponses: 12
    Dernier message: 28/01/2016, 00h54
  2. Réponses: 6
    Dernier message: 15/05/2011, 18h50
  3. [VB6] Mettre en pause l'execution du code
    Par ironik dans le forum VB 6 et antérieur
    Réponses: 8
    Dernier message: 19/05/2006, 10h56
  4. [FLASH MX2004] Comment mettre une pause dans un script
    Par vbcasimir dans le forum Flash
    Réponses: 3
    Dernier message: 16/02/2006, 09h47
  5. Mettre en pause le Timer !
    Par NaDiA_SoFt dans le forum C++Builder
    Réponses: 14
    Dernier message: 12/09/2003, 21h32

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