web-dev-qa-db-fra.com

quelle est la bonne façon d'implémenter un QThread ... (exemple s'il vous plaît ...)

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émentation 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éé, je l'ai fait 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).

J'ai rencontré un problème la semaine dernière (j'ai réussi à passer au travers en travaillant là où j'ai créé mes objets) et j'ai trouvé le article de blog suivant . Voici essentiellement dit que le sous-classement de 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, je suis d'accord avec lui. En suivant les principes de OO, vous ne voulez vraiment que sous-classer une classe pour améliorer encore cette classe ... pas seulement utiliser directement les méthodes des classes ... c'est pourquoi vous instanciez ...

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 billet de blog, il "dit" qu'il a un exemple quelque part ... mais si quelqu'un pouvait me l'expliquer davantage, ce serait grandement apprécié!

Mise à jour:

Étant donné que cette question suscite tant d'attention, voici un copier-coller de la documentation 4.8 avec la manière "appropriée" d'implémenter 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 pense toujours qu'il vaut la peine de souligner qu'ils comprennent un supplément Worker::workerThread membre inutile et qui 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.

61
g19fanatic

La seule chose à laquelle je peux penser est d'ajouter que QObject a une affinité avec un seul thread. Il s'agit généralement du thread qui crée le QObject. Donc, si vous créez un QObject dans le thread principal de l'application et que vous souhaitez l'utiliser dans un autre thread, vous devez utiliser moveToThread() pour changer l'affinité.

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

Ce blog contient un lien vers un exemple . C'est assez court mais ça montre l'idée de base. Créez vos QObject, connectez vos signaux, créez votre QThread, déplacez votre QObjects vers le QThread et démarrez le fil. Les mécanismes de signal/slot garantissent que les limites des threads 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 Nice installations de threading au-delà des threads qui valent probablement la peine de se familiariser avec mais je ne l'ai pas encore fait :)

31
Arnold Spence

Voici n exemple d'utilisation correcte de QThread , mais il a quelques problèmes avec cela, qui sont reflétés dans les commentaires. En particulier, étant donné que l'ordre dans lequel les créneaux sont exécutés n'est pas strictement défini, cela peut entraîner divers problèmes. Le commentaire publié le 6 août 2013 donne une belle 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 pour clarifier.

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 de travail 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, etc. Cependant, il doit garder une trace des états enfants, ce qui signifie utiliser l'appel waitpid () sous Linux ou WaitForMultipleObjects sous Windows. J'avais l'habitude de les appeler en mode non bloquant à l'aide d'une minuterie, mais maintenant je veux plus de réaction rapide, ce qui signifie un mode bloquant. 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 du sort de ses processus enfants (sorti/signalé/Dieu sait ce qui s'est passé). Commence alors 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. Étant donné que l'observateur réside dans le nouveau thread, c'est là que la méthode watch () est exécutée (une connexion en file d'attente est utilisée pour appeler l'emplacement).

Ensuite, je connecte le signal ChildProcesses :: Stop () au slot ChildrenWatcher :: stop () à l'aide de 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 je mets le drapeau arrêté lorsque j'ai besoin de terminer, puis la prochaine fois que isStopped () est appelé, il retourne false et le thread se termine.

Que se passe-t-il donc à la fin de la boucle watch ()? Il appelle deleteLater () afin que l'objet s'autodétruise dès que le contrôle est retourné à la boucle d'événements de thread qui se produit juste après l'appel de deleteLater () (lorsque watch () revient). En revenant à ChildProcesses :: start (), vous pouvez voir qu'il existe une connexion entre le signal destroy () de l'observateur et l'emplacement quit () du thread. Cela signifie que le fil se termine automatiquement lorsque l'observateur a terminé. Et quand il est terminé, il s'autodétruit aussi parce que son propre signal finish () est connecté à son slot deleteLater ().

C'est à peu près la même idée que Maya a posté, mais comme 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. C'est toujours l'autodestruction en premier, arrêtez le fil plus tard, puis il s'autodétruit aussi. Je pourrais définir un signal finish () dans l'ouvrier, puis le connecter à son propre deleteLater (), mais cela ne signifierait qu'une connexion de plus. Comme je n'ai pas besoin d'un signal finish () à d'autres fins, j'ai choisi d'appeler simplement deleteLater () à partir du travailleur lui-même.

Maya mentionne également que vous ne devez pas allouer de nouveaux QObjects au constructeur du travailleur car ils ne vivront pas dans le thread vers lequel vous déplacez le travailleur. Je dirais de le faire de toute façon parce que c'est la façon dont OOP 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, remplacez moveToThread () dans votre travailleur afin qu'il déplace également toutes les choses nécessaires.

9
Sergei Tachenov

Pour ne pas nuire à l'excellente réponse de @ sergey-tachenov, mais en Qt5 vous pouvez arrêter d'utiliser SIGNAL et SLOT, simplifier votre code et avoir l'avantage de compiler la vérification du temps:

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
parsley72

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 l'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 publié 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é this post qui décrit en détail les choses à faire et à ne pas faire du threading dans QT.

À lire avant de décider de sous-classer QThread!

2
Zaid