web-dev-qa-db-fra.com

Signalisation Qt à travers les threads, l'un est le thread GUI?

Que signifie déplacer un objet d'un thread à un autre dans Qt en utilisant moveToThread? Tout semble fonctionner même avant d'utiliser moveToThread, qui déplace l'objet d'un thread (thread GUI) vers un autre thread (travaillé) et Qt: connect appelle l'emplacement approprié sur l'objet.

Y a-t-il une différence en raison de l'emplacement de l'objet, du thread GUI ou du thread de travail?

EDIT: J'ai fait un petit programme, mais je ne comprends pas comment QThread fonctionne avec le signal et la fonction slot, j'apprécierais si vous pouviez expliquer à quoi sert moveToThread avec l'exemple

#include <QtGui/QApplication>
#include <QPushButton>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QString>
#include "mythread.h"
//GUI calls a thread to do some job and sub update the text box once it is done
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget w;
    QHBoxLayout * pH = new QHBoxLayout(&w);
    QPushButton * pushButton = new QPushButton("asdad");
    QLineEdit * lineEdit = new QLineEdit("AAA");
    pH->addWidget(pushButton);
    pH->addWidget(lineEdit);
    w.setLayout(pH);
    w.show();
    MyThread thread;
    qDebug("Thread id %d",(int)QThread::currentThreadId());
    QObject::connect(pushButton,SIGNAL(clicked()),&thread,SLOT(callRun())) ;
    QObject::connect(&thread,SIGNAL(signalGUI(QString)),lineEdit,SLOT(setText(QString)));
    return a.exec();
}

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <QMutex>

class MyThread : public QThread
{
    Q_OBJECT
public:
    MyThread();
public slots:
    void callRun();
    void run();
 signals:
    void signalGUI(QString);
private:
    QMutex mutex;

};

#endif // MYTHREAD_H


#include "mythread.h"
#include <QDebug>
#include <QString>
#include <QMutexLocker>

MyThread::MyThread()
{
}
 void MyThread::callRun()
 {

     qDebug("in thread");
    if(!isRunning())
     {
        this->start(LowestPriority);
        exec();
    }
    else
    {
        run();
    }

 }
 void MyThread::run()
 {
     QMutexLocker fn_scope(&mutex);
     static int a = 0;
    ++a;
     qDebug("Thread id inside run %d",(int)QThread::currentThreadId());
     this->sleep(3);
     static QString number;
     QString temp;
     number += temp.setNum(a);
     emit signalGUI(number);
 }
28

Jetez un œil à Signaux et emplacements sur les threads . Si vous utilisez toujours des signaux et des emplacements pour communiquer avec le thread de travail, Qt gère le moveToThread pour vous si c'est nécessaire et vous avez utilisé la bonne connexion.

Edit: je suppose que l'auteur de l'article voyait son problème car il appelait start dans le constructeur avant que le thread ne soit réellement créé. En d'autres termes, ne faites pas aveuglément confiance au code tiers.

Edit: En réponse à votre commentaire, regardez l'exemple Mandelbrot , sous le MandelbrotWidget Class Implementation entête:

Avec les connexions en file d'attente, Qt doit stocker une copie des arguments qui ont été transmis au signal afin de pouvoir les transmettre ultérieurement à l'emplacement. Qt sait prendre des copies de nombreux types C++ et Qt, mais QImage n'en fait pas partie. Nous devons donc appeler la fonction de modèle qRegisterMetaType () avant de pouvoir utiliser QImage comme paramètre dans les connexions en file d'attente.

Je pense que c'est un peu dépassé, voici les types de méta valides . Étant donné que les signaux et les emplacements sur les threads utilisent des connexions en file d'attente, vous ne devriez pas avoir à effectuer les appels moveToThread dans la plupart des cas.

Edit: je vais essayer d'expliquer les choses avec un exemple similaire:

mythread.h:

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <QMutex>

class MyThread : public QThread
{
   Q_OBJECT

protected:
   virtual void run();

signals:
   void signalGUI(QString);
};

#endif // MYTHREAD_H

mythread.cpp:

#include "mythread.h"
#include <QString>

void MyThread::run()
{
   qDebug("Thread id inside run %d",(int)QThread::currentThreadId());
   static int run = 0;
   QString temp = QString("Run: %1").arg(run++);
   qDebug("String address inside run %p", &temp);
   emit signalGUI(temp);
}

mylineedit.h

#ifndef MYLINEEDIT_H
#define MYLINEEDIT_H

#include <QLineEdit>

class MyLineEdit : public QLineEdit
{
Q_OBJECT
public:
    explicit MyLineEdit(QWidget *parent = 0);

public slots:
    void setText(const QString &string);

};

#endif // MYLINEEDIT_H

mylineedit.cpp

#include "mylineedit.h"
#include <QThread>

MyLineEdit::MyLineEdit(QWidget *parent) :
    QLineEdit(parent)
{
}

void MyLineEdit::setText(const QString &string)
{
   qDebug("Thread id inside setText %d",(int)QThread::currentThreadId());
   qDebug("String address inside setText %p\n", &string);
   QLineEdit::setText(string);
}

main.cpp:

#include <QApplication>
#include <QPushButton>
#include <QHBoxLayout>
#include "mythread.h"
#include "mylineedit.h"

//GUI calls a thread to do some job and sub update the text box once it is done
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   QWidget w;
   QHBoxLayout * pH = new QHBoxLayout(&w);
   QPushButton * pushButton = new QPushButton("Run Thread", &w);
   MyLineEdit * lineEdit = new MyLineEdit(&w);

   pH->addWidget(pushButton);
   pH->addWidget(lineEdit);
   w.show();

   MyThread thread;
   qDebug("Thread id %d",(int)QThread::currentThreadId());
   QObject::connect(pushButton,SIGNAL(clicked()),&thread,SLOT(start())) ;
   QObject::connect(&thread,SIGNAL(signalGUI(const QString&)),lineEdit,SLOT(setText(const QString&)));
   return a.exec();
}

Exemple de sortie après avoir cliqué sur le bouton:

ID de fil 1088110320 
 ID de fil à l'intérieur de l'exécution 1093176208 
 Adresse de chaîne à l'intérieur de l'exécution 0x41288350 
 ID de fil à l'intérieur de setText 1088110320 
 Adresse de chaîne à l'intérieur de setText 0x974af58

Comme vous pouvez le voir, le thread d'exécution est différent du thread principal de l'interface graphique. De plus, même si vous passez une référence const à une chaîne QString, car elle franchit les limites du thread, elle la copie. Je fortement vous encourage à lire Threads et QObject .

37
Adam W
  1. La méthode QThread::start() crée le thread et appelle votre implémentation run(). Si vous souhaitez gérer des événements ou des signaux reçus sur le thread, vous devez appeler QThread::exec()inside votre implémentation run(). Vous ne devez jamais appeler run() explicitement et vous ne devez jamais appeler exec() en dehors de run().

  2. Le thread propriétaire ne fait la différence que lorsqu'un slot est connecté à un signal avec un type de connexion autre que Qt::DirectConnection. Ensuite, Qt s'assurera que l'emplacement s'exécute sur le thread propriétaire, mais pour cela, le thread propriétaire doit exécuter une boucle d'événements avec QThread::exec(). Dans ce cas, l'appel de myObj.moveToThread(myThread) garantira que myObj emplacements s'exécutent sur le thread myThread.

  3. L'objet thread appartient au thread où il a été créé, pas au thread qu'il gère (et où la méthode run s'exécutera). Ainsi, lorsque vous connectez un signal à l'emplacement d'un objet de thread, cet emplacement s'exécutera dans le thread où l'objet de thread a été créé, sauf si vous appelez moveToThread().

8
Dan Berindei

Lorsque vous déplacez un objet entre des threads, vous décidez à quelle boucle d'événement il appartient. Lors de l'établissement de connexions à l'intérieur d'un thread, le code de signalisation appelle directement chacun des emplacements (en attendant qu'ils se terminent). La signalisation au-delà des limites de thread place l'appel de signal sur la boucle d'événement, laissant le thread du slot passer l'appel au slot lorsqu'il est prêt.

Pour effectuer des appels directs entre les threads, vous devez vous assurer que vos fonctions sont réentrantes. Vous devez également vous assurer de protéger vos données à l'aide de mutex ou de sémaphores et en même temps éviter les conditions de concurrence.

Dans l'article, je suppose que le retard est dû au fait que l'appel est direct, c'est-à-dire qu'il n'est pas du tout traité en arrière-plan (mais j'ai seulement survolé le texte).

3
e8johan
#include <QtGui/QApplication>
#include <QPushButton>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QString>
#include "mythread.h"
//GUI calls a thread to do some job and sub update the text box once it is done
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget w;
    QHBoxLayout * pH = new QHBoxLayout(&w);
    QPushButton * pushButton = new QPushButton("asdad");
    QLineEdit * lineEdit = new QLineEdit("AAA");
    pH->addWidget(pushButton);
    pH->addWidget(lineEdit);
    w.setLayout(pH);
    w.show();
    MyThread thread;
    thread.moveToThread(&thread);
    thread.start();
    qDebug("Thread id %d",(int)QThread::currentThreadId());
    QObject::connect(pushButton,SIGNAL(clicked()),&thread,SLOT(callRun()),Qt::QueuedConnection) ;
    QObject::connect(&thread,SIGNAL(signalGUI(QString)),lineEdit,SLOT(setText(QString)),Qt::DirectConnection);
    return a.exec();
}

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <QMutex>

class MyThread : public QThread
{
    Q_OBJECT
public:
    MyThread();
public slots:
    void callRun();
    void run();
 signals:
    void signalGUI(QString);
private:
    QMutex mutex;

};

#endif // MYTHREAD_H
#include "mythread.h"
#include <QDebug>
#include <QString>
#include <QMutexLocker>

MyThread::MyThread()
{
}
 void MyThread::callRun()
 {
     QMutexLocker fn_scope(&mutex);
     static int a = 0;
    ++a;
     qDebug("Thread id inside run %d",(int)QThread::currentThreadId());
     this->sleep(3);
     static QString number;
     QString temp;
     number += temp.setNum(a);
     emit signalGUI(number);

 }
 void MyThread::run()
 {
    exec();
 }

Un nouvel objet thread est créé et l'objet thread est déplacé vers le même thread. Les signaux sont maintenant à travers les threads et le type de connexion sont tous deux en file d'attente et cela fonctionne comme prévu.

3

certains objets ne peuvent être utilisés que sur le thread propriétaire. par exemple, si vous créez et socket objet dans un thread et que vous souhaitez envoyer et récupérer des données dans un autre thread, il n'est pas possible. par conséquent, une solution consiste à déplacer votre objet d'un fil à l'autre et à opérer dessus.

0
chezgi