Quelle est la bonne façon d'implémenter un QThread... (exemple svp…)

La documentation Qt pour QThread indique de créer une classe à partir de QThread et d'implémenter la méthode run.

Ci-dessous est tiré de la documentation 4.7 Qthread...

Pour créer vos propres threads, sous-classe QThread et réimplémenter run (). Par exemple:

 class MyThread : public QThread
 {
 public:
     void run();
 };

 void MyThread::run()
 {
     QTcpSocket socket;
     // connect QTcpSocket's signals somewhere meaningful
     ...
     socket.connectToHost(hostName, portNumber);
     exec();
 }

Donc, dans chaque thread que j'ai créé, j'ai fait cela et pour la plupart des choses, cela fonctionne très bien (je n'implémente moveToThread (this) dans aucun de mes objets et cela fonctionne très bien).

Je j'ai eu un accroc la semaine dernière (j'ai réussi à le surmonter en travaillant là où j'ai créé mes objets) et j'ai trouvé le suivant le blog . Voici essentiellement dit que le sous-classement QThread n'est vraiment pas la bonne façon de le faire (et que la documentation est incorrecte).

Cela vient d'un développeur Qt donc à première vue, j'étais intéressé et après réflexion, d'accord avec lui. En suivant les principes OO, vous voulez vraiment seulement sous-classer une classe pour améliorer davantage cette classe... ne pas simplement utiliser les méthodes de classes directement... c'est pourquoi vous instancier...

Disons que je voulais déplacer une classe QObject personnalisée vers un thread... quelle serait la manière "correcte" de le faire? Dans ce blog, il dit " il a un exemple quelque part... mais si quelqu'un pourrait-il m'expliquer ce serait grandement apprécié!

Mise à Jour:

Puisque cette question attire tant d'attention, voici un copier-coller de la documentation 4.8 avec la manière 'appropriée' de implémentez un QThread.

class Worker : public QObject
 {
     Q_OBJECT
     QThread workerThread;

 public slots:
     void doWork(const QString &parameter) {
         // ...
         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, SIGNAL(finished()), worker, SLOT(deleteLater()));
         connect(this, SIGNAL(operate(QString)), worker, SLOT(doWork(QString)));
         connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString)));
         workerThread.start();
     }
     ~Controller() {
         workerThread.quit();
         workerThread.wait();
     }
 public slots:
     void handleResults(const QString &);
 signals:
     void operate(const QString &);
 };

Je crois toujours qu'il vaut la peine de souligner qu'ils incluent un membre Worker::workerThread supplémentaire qui est inutile et n'est jamais utilisé dans leur exemple. Retirez cette pièce et c'est un bon exemple de la façon de faire du filetage dans Qt.

58
demandé sur Mogsdad 2010-11-04 04:31:53

4 réponses

À propos de la seule chose que je peux penser à ajouter est d'indiquer davantage que QObjects ont une affinité avec un seul thread. C'est généralement le thread qui crée le QObject. Donc, si vous créez un QObject dans le thread principal de l'application et que vous souhaitez utiliser dans un autre thread, vous devez utiliser moveToThread() pour modifier l'affinité.

Cela évite d'avoir à sous-classer QThread et à créer vos objets dans la méthode run(), gardant ainsi vos objets bien encapsulés.

Ce billet de blog inclut un lien vers un exemple . Il est assez court, mais il montre l'idée de base. Créez votre QObjects, connecter vos signaux, créer votre QThread, déplacez votre QObjects de la QThread et démarrer le thread. Les mécanismes de signal/fente assureront que les limites de fil sont franchies correctement et en toute sécurité.

Vous devrez peut-être introduire la synchronisation si vous devez appeler des méthodes sur votre objet en dehors de ce mécanisme.

Je sais que Qt a d'autres fonctionnalités de filetage au-delà des threads cela vaut probablement la peine de se familiariser avec mais je ne l'ai pas encore fait:)

31
répondu Arnold Spence 2016-04-30 15:08:52

Voici un exemple d'utilisation correcte de QThread, mais il a quelques problèmes, qui sont reflétés dans les commentaires. En particulier, puisque l'ordre dans lequel les fentes sont exécutées n'est pas strictement défini, cela pourrait conduire à divers problèmes. Le commentaire posté le août 6, 2013 donne une bonne idée de la façon de traiter ce problème. J'utilise quelque chose comme ça dans mon programme, et voici un exemple de code à préciser.

L'idée de base est la même: je crée une instance QThread qui vit dans mon thread principal, une instance de classe worker qui vit dans le nouveau thread que j'ai créé, puis je connecte tous les signaux.

void ChildProcesses::start()
{
    QThread *childrenWatcherThread = new QThread();
    ChildrenWatcher *childrenWatcher = new ChildrenWatcher();
    childrenWatcher->moveToThread(childrenWatcherThread);
    // These three signals carry the "outcome" of the worker job.
    connect(childrenWatcher, SIGNAL(exited(int, int)),
            SLOT(onChildExited(int, int)));
    connect(childrenWatcher, SIGNAL(signalled(int, int)),
            SLOT(onChildSignalled(int, int)));
    connect(childrenWatcher, SIGNAL(stateChanged(int)),
            SLOT(onChildStateChanged(int)));
    // Make the watcher watch when the thread starts:
    connect(childrenWatcherThread, SIGNAL(started()),
            childrenWatcher, SLOT(watch()));
    // Make the watcher set its 'stop' flag when we're done.
    // This is performed while the watch() method is still running,
    // so we need to execute it concurrently from this thread,
    // hence the Qt::DirectConnection. The stop() method is thread-safe
    // (uses a mutex to set the flag).
    connect(this, SIGNAL(stopped()),
            childrenWatcher, SLOT(stop()), Qt::DirectConnection);
    // Make the thread quit when the watcher self-destructs:
    connect(childrenWatcher, SIGNAL(destroyed()),
            childrenWatcherThread, SLOT(quit()));
    // Make the thread self-destruct when it finishes,
    // or rather, make the main thread delete it:
    connect(childrenWatcherThread, SIGNAL(finished()),
            childrenWatcherThread, SLOT(deleteLater()));
    childrenWatcherThread->start();
}

Quelques antécédents:

La classe ChildProcesses est un gestionnaire de processus enfant qui démarre de nouveaux processus enfants avec des appels spawn (), conserve la liste des processus en cours d'exécution et ainsi de suite. Cependant, il doit garder une trace des États enfants, ce qui signifie utiliser l'appel waitpid () sur Linux ou WaitForMultipleObjects sur Windows. J'ai l'habitude d'appeler ces dans mode non bloquant en utilisant une minuterie, mais maintenant je veux une réaction plus rapide, ce qui signifie le mode de blocage. C'est là que le fil entre en jeu.

La classe ChildrenWatcher est définie comme suit:

class ChildrenWatcher: public QObject {
    Q_OBJECT
private:
    QMutex mutex;
    bool stopped;
    bool isStopped();
public:
    ChildrenWatcher();
public slots:
    /// This is the method which runs in the thread.
    void watch();
    /// Sets the stop flag.
    void stop();
signals:
    /// A child process exited normally.
    void exited(int ospid, int code);
    /// A child process crashed (Unix only).
    void signalled(int ospid, int signal);
    /// Something happened to a child (Unix only).
    void stateChanged(int ospid);
};

Voici comment cela fonctionne. Lorsque tout cela est démarré, la méthode ChildProcess:: start() est appelée (voir ci-dessus). Il crée un nouveau QThread et un nouveau ChildrenWatcher, qui est ensuite déplacé vers le nouveau thread. Ensuite je connecte trois signaux qui informent mon manager sur le sort de ses processus enfants (sortie / signalé / Dieu-sait-ce qui s'est passé). Puis commence le plaisir principal.

Je connecte QThread::started() à la méthode ChildrenWatcher:: watch () afin qu'il soit démarré dès que le thread est prêt. Puisque l'observateur vit dans le nouveau thread, c'est là que la méthode watch() est exécutée (la connexion en file d'attente est utilisée pour appeler l'emplacement).

Ensuite, je connecte le signal ChildProcesses:: stopped() au Slot ChildrenWatcher:: stop () en utilisant Qt:: DirectConnection car je dois le faire de manière asynchrone. Ceci est nécessaire pour que mon thread s'arrête lorsque le Gestionnaire ChildProcesses n'est plus nécessaire. La méthode stop() ressemble à ceci:

void ChildrenWatcher::stop()
{
    mutex.lock();
    stopped = true;
    mutex.unlock();
}

Et puis ChildrenWatcher:: watch ():

void ChildrenWatcher::watch()
{
  while (!isStopped()) {
    // Blocking waitpid() call here.
    // Maybe emit one of the three informational signals here too.
  }
  // Self-destruct now!
  deleteLater();
}

Oh, et la méthode isStopped () est juste un moyen pratique d'utiliser un mutex dans la condition while ():

bool ChildrenWatcher::isStopped()
{
    bool stopped;
    mutex.lock();
    stopped = this->stopped;
    mutex.unlock();
    return stopped;
}

Donc, ce qui se passe ici, c'est que j'ai défini le drapeau arrêté quand j'ai besoin de terminer, puis la prochaine fois qu'isStopped() est appelé, il retourne false et le thread se termine.

Alors, que se passe-t-il quand la boucle watch() se termine? Il appelle deleteLater() de sorte que l'objet s'autodétruit dès que le contrôle est retourné à la boucle d'événement thread qui se produit juste après l'appel deleteLater () (lorsque watch () revient). Pour en revenir à ChildProcesses:: start (), vous pouvez voir qu'il existe une connexion du signal destroyed () de l'observateur à la fente quit () du thread. Cela signifie que le thread se termine automatiquement lorsque l'observateur est fait. Et quand il est terminé, il s'autodétruit aussi parce que son propre fini() signal est connecté à son deleteLater() fente.

C'est à peu près la même idée que Maya affichée, mais parce que j'utilise l'idiome d'autodestruction, Je n'ai pas besoin de dépendre de la séquence dans laquelle les slots sont appelés. Il s'autodétruit toujours d'abord, arrête le thread plus tard, puis il s'autodétruit aussi. Je pourrais définir un signal finished() dans le worker, puis le connecter à son propre deleteLater (), mais cela ne signifierait qu'une connexion de plus. Puisque je n'ai pas besoin d'un signal fini() pour tout autre but, j'ai choisi d'appeler simplement deleteLater() du travailleur lui-même.

Maya mentionne également que vous ne devriez pas allouer de nouveaux QObjects dans le constructeur du worker car ils ne vivront pas dans le thread vers lequel vous déplacez le worker. Je dirais de le faire de toute façon parce que C'est la façon dont la POO fonctionne. Assurez-vous simplement que tous ces QObjects sont des enfants du travailleur(c'est - à-dire, utilisez le constructeur QObject (QObject*)) - moveToThread () déplace tous les enfants avec l'objet déplacé. Si vous avez vraiment besoin D'avoir des QObjects qui ne sont pas des enfants de votre objet, puis remplacez moveToThread() dans votre travailleur afin qu'il déplace toutes les choses nécessaires aussi.

8
répondu Sergei Tachenov 2013-08-16 22:07:41

Pour ne pas nuire à l'excellente réponse de @sergey-tachenov, mais dans Qt5 vous pouvez arrêter D'utiliser SIGNAL et SLOT, simplifier votre code et avoir l'avantage de vérifier la compilation:

void ChildProcesses::start()
{
    QThread *childrenWatcherThread = new QThread();
    ChildrenWatcher *childrenWatcher = new ChildrenWatcher();
    childrenWatcher->moveToThread(childrenWatcherThread);
    // These three signals carry the "outcome" of the worker job.
    connect(childrenWatcher, ChildrenWatcher::exited,
            ChildProcesses::onChildExited);
    connect(childrenWatcher, ChildrenWatcher::signalled,
            ChildProcesses::onChildSignalled);
    connect(childrenWatcher, ChildrenWatcher::stateChanged,
            ChildProcesses::onChildStateChanged);
    // Make the watcher watch when the thread starts:
    connect(childrenWatcherThread, QThread::started,
            childrenWatcher, ChildrenWatcher::watch);
    // Make the watcher set its 'stop' flag when we're done.
    // This is performed while the watch() method is still running,
    // so we need to execute it concurrently from this thread,
    // hence the Qt::DirectConnection. The stop() method is thread-safe
    // (uses a mutex to set the flag).
    connect(this, ChildProcesses::stopped,
            childrenWatcher, ChildrenWatcher::stop, Qt::DirectConnection);
    // Make the thread quit when the watcher self-destructs:
    connect(childrenWatcher, ChildrenWatcher::destroyed,
            childrenWatcherThread, QThread::quit);
    // Make the thread self-destruct when it finishes,
    // or rather, make the main thread delete it:
    connect(childrenWatcherThread, QThread::finished,
            childrenWatcherThread, QThread::deleteLater);
    childrenWatcherThread->start();
}
3
répondu parsley72 2014-04-27 08:39:15

Le sous-classement de la classe qthread exécutera toujours le code dans le thread d'origine. Je voulais exécuter un écouteur udp dans une application qui utilise déjà le Thread GUI (le thread principal) et pendant que mon écouteur udp fonctionnait parfaitement, mon interface graphique était gelée car elle était bloquée par les gestionnaires d'événements QThread sous-Classés. Je pense que ce que g19fanatic a posté est correct mais vous aurez également besoin du thread de travail pour migrer avec succès l'objet vers le nouveau thread. J'ai trouvé ce post qui décrit dans détails des choses à faire et à ne pas faire du threading dans QT.

Doit lire avant de décider de sous-classe QThread !

2
répondu Zaid 2018-02-14 23:25:02