Qt сигнализация через потоки, один поток GUI? - PullRequest
28 голосов
/ 18 января 2010

Что означает перемещение объекта из одного потока в другой в Qt с помощью moveToThread? Кажется, что все работает даже до использования moveToThread, который перемещает объект из одного потока (поток GUI) в другой поток (работает), и Qt: connect вызывает соответствующий слот объекта.

Есть ли какая-либо разница из-за того, где находится объект, поток GUI или рабочий поток?

EDIT: Я сделал небольшую программу, но я не понимаю, как QThread работает вместе с функцией сигнала и слота, я был бы признателен, если бы вы могли объяснить, как используется moveToThread с примером

#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);
 }

Ответы [ 5 ]

37 голосов
/ 18 января 2010

Взгляните на Сигналы и слоты между потоками . Если вы всегда используете сигналы и слоты для связи с рабочим потоком, Qt обрабатывает moveToThread для вас, если это необходимо, и вы использовали правильное соединение.

Редактировать: Я предполагаю, что автор статьи видел свою проблему, так как он вызывал start в конструкторе до того, как поток был фактически создан. Другими словами, не доверяйте стороннему коду вслепую.

Редактировать: В ответ на ваш комментарий посмотрите на пример Мандельброта под заголовком MandelbrotWidget Class Implementation:

В случае соединений с очередями Qt должен хранить копию аргументов, которые были переданы сигналу, чтобы впоследствии он мог передать их в слот. Qt знает, как взять копию многих типов C ++ и Qt, но QImage не является одним из них. Поэтому мы должны вызвать функцию шаблона qRegisterMetaType (), прежде чем мы сможем использовать QImage в качестве параметра в подключениях в очереди.

Я считаю, что это немного устарело, вот допустимых мета-типов . Поскольку сигналы и слоты между потоками используют соединения в очереди, в большинстве случаев вам не нужно выполнять вызовы moveToThread.

Edit: Я попытаюсь объяснить вещи с подобным примером:

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();
}

Пример вывода после нажатия кнопки:

Thread id 1088110320
Thread id inside run 1093176208
String address inside run 0x41288350
Thread id inside setText 1088110320
String address inside setText 0x974af58

Как видите, поток выполнения отличается от основного потока GUI. Кроме того, даже если вы передаете константную ссылку на QString, поскольку она пересекает границы потоков, она копирует ее. Я настоятельно рекомендую прочитать Threads и QObject .

8 голосов
/ 22 января 2010
  1. Метод QThread::start() создает поток и вызывает вашу реализацию run(). Если вы хотите обрабатывать события или полученные сигналы в потоке, вы должны вызвать QThread::exec() внутри вашей run() реализации. Вы никогда не должны звонить run() явно и никогда не должны звонить exec() за пределами run().

  2. Поток владельца имеет значение, только если слот подключен к сигналу с типом соединения, отличным от Qt::DirectConnection. Затем Qt будет гарантировать, что слот работает в потоке владельца, но для этого поток владельца должен запустить цикл обработки событий с QThread::exec(). В этом случае вызов myObj.moveToThread(myThread) обеспечит выполнение слотов myObj в потоке myThread.

  3. Объект потока принадлежит потоку, в котором он был создан, а не тому потоку, которым он управляет (и где будет выполняться метод run). Поэтому, когда вы подключаете сигнал к слоту объекта потока, этот слот будет работать в потоке, в котором был создан объект потока, если вы не вызовете moveToThread().

3 голосов
/ 20 января 2010
#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();
 }

Новый объект потока создан, и объект потока перемещен в тот же поток. Сигналы теперь передаются по потокам, а тип подключения - очередь, и все работает как положено.

3 голосов
/ 18 января 2010

При перемещении объекта между потоками вы решаете, к какому циклу событий он принадлежит. При создании соединений внутри потока сигнальный код напрямую вызывает каждый из слотов (приходится ждать, пока они не завершатся). Сигнализация через границы потоков помещает сигнальный вызов в цикл обработки событий, позволяя потоку слота выполнять вызов в слот, когда он будет готов.

Выполнение прямых вызовов между потоками требует от вас убедиться, что ваши функции реентерабельны. Вы также должны защищать свои данные, используя мьютексы или семафоры, и в то же время избегать условий гонки.

В этой статье я полагаю, что задержка вызвана тем, что вызов является прямым, то есть не обрабатывается в фоновом режиме (но я только просматривал текст).

0 голосов
/ 19 января 2010

некоторые объекты могут быть использованы только в ветке владельца. например, если вы создаете объект сокета в одном потоке и хотите отправлять и получать данные в другом потоке, это невозможно. поэтому одним из решений является перемещение вашего объекта из одного потока в другой и работа с ним.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...