Qt сигнализирует между потоками, один из которых - GUI thread?

StackOverflow https://stackoverflow.com/questions/2086142

Вопрос

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

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

Редактировать:Я создал небольшую программу, но я не понимаю, как QThread работает вместе с функцией Signal и slot, я был бы признателен, если бы вы могли объяснить, в чем заключается использование 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);
 }
Это было полезно?

Решение

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

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

Редактировать:В ответ на ваш комментарий, посмотрите на Мандельброт например, в рамках 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

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

моя линияправить.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

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

Другие советы

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

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

  3. Объект thread принадлежит потоку, в котором он был создан, а не потоку, которым он управляет (и где будет выполняться метод run).Таким образом, когда вы подключаете сигнал к слоту объекта thread, этот слот будет выполняться в потоке, где был создан объект thread, если вы не вызовете 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;
    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();
 }

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top