Сигнализация Qt между потоками, один из потоков графического интерфейса?

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

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

РЕДАКТИРОВАТЬ: я сделал небольшую программу, но я не понимаю, как QThread работает вместе с функцией Signal и слотом, я был бы признателен, если бы вы могли объяснить, что такое использование 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);
 }

person Passionate programmer    schedule 18.01.2010    source источник


Ответы (5)


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

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

Изменить: в ответ на ваш комментарий посмотрите на Mandelbrot пример, под заголовком MandelbrotWidget Class Implementation:

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

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

Изменить: я попытаюсь объяснить ситуацию на аналогичном примере:

мифочит.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

мифочит.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);
}

основной.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

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

person Adam W    schedule 18.01.2010
comment
Я не видел никакой документации, в которой говорилось бы о том, что Qt обрабатывает moveToThread, я читал о том, что Qt обрабатывает тип соединения слота сигнала в зависимости от того, где находится объект. - person Passionate programmer; 19.01.2010
comment
Обновленный ответ, я, возможно, немного упростил, но в большинстве случаев он справится с работой. - person Adam W; 19.01.2010
comment
@Adam Что вы подразумеваете под Qt обрабатывает moveToThread для вас, если это необходимо, и вы использовали правильное соединение. В соответствии с примером connect(&thread, SIGNAL(renderedImage(const QImage &, double)), this, SLOT(updatePixmap(const QImage &, double))); thread и this все объекты из одного потока, но как соединение ставится в очередь? - person Passionate programmer; 19.01.2010
comment
@Beginner: QImage, который вы передаете в сигнал/слот, копируется в поток слота, если только они не находятся в одном потоке, когда вы используете соединение в очереди (или автоматическое). И будьте осторожны, thread был создан в том же потоке, что и this, но как только вы правильно запустите поток, он запустит другой цикл обработки событий. - person Adam W; 19.01.2010
comment
@ Адам, можешь попробовать объяснить мне код, который я добавил :) - person Passionate programmer; 20.01.2010
comment
@ Адам, спасибо, ты всегда отвечаешь на некоторые вопросы, которые я не задавал, но спасибо за ответы, которых я не знаю. Пожалуйста, подожди пару дней, дай мне посмотреть, кто-нибудь другой ответит лучше, чем ты. - person Passionate programmer; 20.01.2010
comment
Я не использовал ваш пример, потому что он не показал, почему moveToThread не нужен. Вам нужно было увидеть, что адрес данных, прошедших через сигнал/слот, изменился, чтобы понять, почему вам не нужно перемещать данные. - person Adam W; 20.01.2010
comment
@Adam: Спасибо, я запутался в основном в том, где следует использовать функции start , exec, run. Думаю, теперь я в порядке. - person Passionate programmer; 20.01.2010
comment
@Adam: Можно ли снова и снова вызывать exec() в данном потоке? - person Passionate programmer; 20.01.2010
comment
@Adam: Почему механизм сигнального слота работает в SubThread без exec; цикл событий потока - person Passionate programmer; 27.01.2010
comment
@Adam W, ты только что получил 100 баллов. Если вы хотите добавить какое-либо объяснение, пожалуйста, добавьте, спасибо - person Passionate programmer; 27.01.2010
comment
Единственное, что я могу добавить, это то, что если вы хотите, чтобы события запускались внутри потока, вам нужно вызвать exec() из вашей функции run(). Не забывайте избегать слотов в потоке, если вы не используете moveToThread(), сигналы не требуют этого. - person Adam W; 27.01.2010
comment
@RicardoE: см.: QObject::connect(pushButton,SIGNAL(clicked()),&thread,SLOT(start())); - person Adam W; 06.11.2012

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

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

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

person Dan Berindei    schedule 22.01.2010

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

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

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

person e8johan    schedule 18.01.2010
comment
Я думаю, что он использовал соединение с очередью, даже если соединение находится между объектами из одного потока, поэтому графический интерфейс ждал завершения некоторых других операций перед выполнением слота для нажатия клавиши (слот не должен быть повторным), а затем зависает графический интерфейс во время выполнения слота также. Верна ли моя интерпретация? - person yesraaj; 18.01.2010
comment
Вы оба близки, но Qt обрабатывает передачу данных, когда вы используете правильные соединения для сигналов и слотов. Я думаю, что пример, на который он смотрит, является проблемой. - person Adam W; 18.01.2010
comment
@ e8johan убедитесь, что ваши функции реентерабельны по функции, которую вы имели в виду, верно? - person Passionate programmer; 19.01.2010
comment
@e8johan Передача сигналов через границы потоков помещает вызов сигнала в цикл обработки событий, означает ли это, что излучается сигнал1; поток отличается от потока объекта функции слота. Или испускать объект и получать объекты из разных потоков? - person Passionate programmer; 19.01.2010
comment
@Beginner: слоты - это функции. И эмиты размещаются в текущем запущенном потоке (будь то GUI или рабочий). Если в другом потоке есть слот, который подключен к этому сигналу, он будет работать в потоке владельца слота, когда он будет извлечен из очереди событий. - person Adam W; 19.01.2010
comment
@Beginner: @Adam красиво выразился. - person e8johan; 19.01.2010
comment
@Adam @e8johan, значит, тип соединения определяется только во время выполнения, а также в зависимости от того, где находится излучение? - person yesraaj; 19.01.2010
comment
@yesraaj: тип соединения определяется либо во время выполнения, либо во время компиляции в зависимости от того, как вы объявляете соединение. См.: doc.trolltech.com/4.6/qt.html#ConnectionType-enum< /а> - person Adam W; 19.01.2010
comment
@ Адам, я смотрю на ссылку, которую ты дал, но я не понимаю, как они устанавливают связь !!! - person yesraaj; 19.01.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();
 }

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

person Passionate programmer    schedule 20.01.2010
comment
Возможно, это опечатка, но в вашей функции main(...) вы явно запрашиваете Qt::DirectConnection, и это может привести к катастрофе, учитывая, что сигнал испускается объектом MyThread и, следовательно, графический интерфейс, касающийся слота setText(...), выполняется в контексте другого потока. чем основной поток. - person axxel; 21.02.2013

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

person chezgi    schedule 19.01.2010