Документация Qt для QThread говорит о создании класса из QThread и реализации метода run.
Ниже взята из документации 4.7 Qthread...
Чтобы создать свои собственные потоки, подкласс QThread и reimplement run(). Например:
class MyThread : public QThread
{
public:
void run();
};
void MyThread::run()
{
QTcpSocket socket;
// connect QTcpSocket signals somewhere meaningful
...
socket.connectToHost(hostName, portNumber);
exec();
}
Итак, в каждом отдельном потоке, который я создал, я сделал именно это, и для большинства вещей он работает просто отлично (я не реализую moveToThread (это) в любом из моих объектов, и он отлично работает).
На прошлой неделе я попал в ловушку (удалось пройти через нее, работая там, где я создал свои объекты), и нашел следующее сообщение в блоге. Здесь в основном говорится, что подклассификация QThread действительно не является правильным способом ее выполнения (и что документация неверна).
Это происходит от разработчика Qt, поэтому, на первый взгляд, меня заинтересовало и на дальнейшее размышление, согласитесь с ним. Следуя принципам OO, вы действительно хотите только подклассы класса для дальнейшего улучшения этого класса... не просто использовать методы классов напрямую... вот почему вы создаете экземпляр...
Предположим, что я хотел переместить пользовательский класс QObject в поток... что было бы "правильным" способом сделать это? В этом сообщении в блоге он "говорит", что у него есть пример где-то... но если бы кто-то мог объяснить это мне, это было бы очень признательно!
Update:
Так как этот вопрос получает так много внимания, вот копия и вставка документации 4.8 с "правильным" способом реализации QThread.
class Worker : public QObject
{
Q_OBJECT
QThread workerThread;
public slots:
void doWork(const QString ¶meter) {
// ...
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 &);
};
Я по-прежнему считаю, что стоит отметить, что они включают дополнительный Worker::workerThread
элемент, который не нужен и никогда не используется в их примере. Удалите эту деталь, и это правильный пример того, как выполнять потоки в Qt.
Ответ 1
Единственное, что я могу добавить, это добавить, что QObject
имеет сходство с одним потоком. Обычно это поток, который создает QObject
. Поэтому, если вы создаете QObject
в главном потоке приложения и хотите использовать его в другом потоке, вам нужно использовать moveToThread()
для изменения сродства.
Это избавляет от необходимости создавать подклассы QThread
и создавать ваши объекты в методе run()
, таким образом сохраняя ваши вещи красиво инкапсулированными.
В этом блоге есть ссылка на пример. Это довольно короткий, но он показывает основную идею. Создайте свои QObject
, соедините ваши сигналы, создайте свой QThread
, переместите свой QObjects
в QThread
и запустите поток. Механизмы сигнал/слот обеспечат правильное и безопасное пересечение границ потоков.
Возможно, вам придется ввести синхронизацию, если вам нужно вызывать методы для вашего объекта вне этого механизма.
Я знаю, что в Qt есть и другие хорошие средства для создания потоков, помимо потоков, с которыми, вероятно, стоит познакомиться, но я еще не сделал этого :)
Ответ 2
Здесь один пример того, как правильно использовать QThread, но у него есть некоторые проблемы с ним, которые отражены в комментариях. В частности, поскольку порядок, в котором выполняются слоты, строго не определен, это может привести к различным проблемам. Комментарий, отправленный 6 августа 2013 года, дает прекрасную идею о том, как справиться с этой проблемой. Я использую что-то подобное в своей программе, и вот пример кода для уточнения.
Основная идея такая же: я создаю экземпляр QThread, который живет в моем основном потоке, экземпляр рабочего класса, который живет в новом потоке, который я создал, и затем подключаю все сигналы.
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();
}
Некоторая предыстория:
Класс ChildProcesses - это дочерний процесс-менеджер, который запускает новые дочерние процессы с вызовами spawn(), сохраняет список запущенных процессов и так далее. Тем не менее, он должен отслеживать состояния детей, что означает использование waitpid() для вызова в Linux или WaitForMultipleObjects в Windows. Я использовал их в неблокирующем режиме, используя таймер, но теперь мне нужна более оперативная реакция, что означает режим блокировки. Что там, где входит нить.
Класс ChildrenWatcher определяется следующим образом:
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);
};
Вот как это работает. Когда все это происходит, вызывается метод ChildProcess:: start() (см. Выше). Он создает новый QThread и новый KidsWatcher, который затем перемещается в новый поток. Затем я соединяю три сигнала, которые информируют моего менеджера о судьбе его дочерних процессов (выходят/сигнализируются/бог-знает-что-то случилось). Затем начинается основное развлечение.
Я подключаю QThread:: started() к методу ChildrenWatcher:: watch(), поэтому он запускается, как только поток готов. Поскольку наблюдатель живет в новом потоке, то, где выполняется метод watch() (для вызова слота используется очередь в очереди).
Затем я подключаю сигнал ChildProcesses:: stopped() к слоту ChildrenWatcher:: stop(), используя Qt:: DirectConnection, потому что мне нужно сделать это асинхронно. Это необходимо, поэтому мой поток останавливается, когда менеджер ChildProcesses больше не нужен. Метод stop() выглядит следующим образом:
void ChildrenWatcher::stop()
{
mutex.lock();
stopped = true;
mutex.unlock();
}
И затем 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();
}
О, и метод isStopped() - это просто удобный способ использования мьютекса в условии while():
bool ChildrenWatcher::isStopped()
{
bool stopped;
mutex.lock();
stopped = this->stopped;
mutex.unlock();
return stopped;
}
Итак, что здесь происходит, так это то, что я устанавливаю флаг остановки, когда мне нужно закончить, а затем в следующий раз isStopped() вызывается, он возвращает false и заканчивается поток.
Итак, что происходит, когда цикл watch() заканчивается? Он вызывает deleteLater(), поэтому объект самоуничтожается, как только элемент управления возвращается в цикл событий потока, который происходит сразу после вызова deleteLater() (когда функция watch() возвращает). Возвращаясь к ChildProcesses:: start(), вы можете увидеть, что есть соединение от сигнала destroy() наблюдателя к слоту quit() потока. Это означает, что поток автоматически заканчивается, когда наблюдатель сделан. И когда он заканчивается, он самоуничтожается, потому что его собственный законченный() сигнал подключен к слоту deleteLater().
Это почти та же идея, что и Майя, но поскольку я использую идиому саморазрушения, мне не нужно зависеть от последовательности, в которой вызывается слот. Он всегда самоуничтоживает сначала, останавливает поток позже, затем сам себя разрушает. Я мог бы определить завершенный() сигнал в рабочем месте, а затем подключить его к собственному deleteLater(), но это будет означать только одно соединение. Поскольку мне не нужен готовый() сигнал для каких-либо других целей, я решил просто вызвать deleteLater() из самого рабочего.
Майя также упоминает, что вы не должны выделять новые QObjects в конструкторе рабочего, потому что они не будут жить в потоке, в который вы перемещаете рабочего. Я бы сказал, все равно, потому что это работает ООП. Просто убедитесь, что все эти QObjects являются дочерними элементами рабочего (т.е. Используют конструктор QObject (QObject *)). MoveToThread() перемещает все дочерние элементы вместе с перемещаемым объектом. Если вам действительно нужны QObjects, которые не являются дочерними элементами вашего объекта, тогда переопределите moveToThread() в вашем рабочем месте, чтобы он также перемещал все необходимые материалы.
Ответ 3
Не отвлекать от @sergey-tachenov отличный ответ, но в Qt5 вы можете прекратить использовать SIGNAL и SLOT, упростить свой код и иметь преимущество проверки времени компиляции:
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();
}
Ответ 4
наследование класса qthread все равно будет запускать код в исходном потоке. Я хотел запустить слушатель udp в приложении, которое уже использует поток GUI (основной поток), и в то время как мой слушатель udp работал отлично, мой графический интерфейс был заморожен, так как он был заблокирован обработчиками событий qthread с подклассами. Я думаю, что опубликованная g19fanatic правильная информация, но вам также понадобится рабочий поток, чтобы успешно перенести объект в новый поток. Я нашел этот пост, в котором подробно описывается, что можно и чего нельзя делать в QT.
Необходимо прочитать, прежде чем вы решите подкласс QThread!
Ответ 5
Моя версия наилучшей модели потоков в Qt5
так проста:
worker.h
:
/*
* This is a simple, safe and best-practice way to demonstrate how to use threads in Qt5.
* Copyright (C) 2019 Iman Ahmadvand
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef _WORKER_H
#define _WORKER_H
#include <QtCore/qobject.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qthread.h>
#include <QtGui/qevent.h>
namespace concurrent {
class EventPrivate;
class Event : public QEvent {
public:
enum {
EventType1 = User + 1
};
explicit Event(QEvent::Type);
Event(QEvent::Type, const QByteArray&);
void setData(const QByteArray&);
QByteArray data() const;
protected:
EventPrivate* d;
};
class WorkerPrivate;
/* A worker class to manage one-call and permanent tasks using QThread object */
class Worker : public QObject {
Q_OBJECT
public:
Worker(QThread*);
~Worker();
protected slots:
virtual void init();
protected:
bool event(QEvent*) override;
protected:
WorkerPrivate* d;
signals:
/* this signals is used for one call type worker */
void finished(bool success);
};
} // namespace concurrent
#endif // !_WORKER_H
worker.cpp
:
/*
* This is a simple, safe and best-practice way to demonstrate how to use threads in Qt5.
* Copyright (C) 2019 Iman Ahmadvand
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "worker.h"
using namespace concurrent;
class concurrent::EventPrivate {
public:
QByteArray data;
};
Event::Event(QEvent::Type t):QEvent(t), d(new EventPrivate) {
setAccepted(false);
}
Event::Event(QEvent::Type t, const QByteArray& __data) : Event(t) {
setData(__data);
}
void Event::setData(const QByteArray& __data) {
d->data = __data;
}
QByteArray Event::data() const {
return d->data;
}
class concurrent::WorkerPrivate {
public:
WorkerPrivate() {
}
};
Worker::Worker(QThread* __thread) :QObject(nullptr), d(new WorkerPrivate) {
moveToThread(__thread);
QObject::connect(__thread, &QThread::started, this, &Worker::init);
QObject::connect(this, &Worker::finished, __thread, &QThread::quit);
QObject::connect(__thread, &QThread::finished, __thread, &QThread::deleteLater);
QObject::connect(__thread, &QThread::finished, this, &Worker::deleteLater);
}
Worker::~Worker() {
/* do clean up if needed */
}
void Worker::init() {
/* this will called once for construction and initializing purpose */
}
bool Worker::event(QEvent* e) {
/* event handler */
if (e->type() == Event::EventType1) {
/* do some work with event data and emit signals if needed */
auto ev = static_cast<Event*>(e);
ev->accept();
}
return QObject::event(e);
}
usage.cpp
:
#include <QtCore/qcoreapplication.h>
#include "worker.h"
using namespace concurrent;
Worker* create(bool start) {
auto worker = new Worker(new QThread);
if (start)
worker->thread()->start();
return worker;
}
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
auto worker = create(true);
if (worker->thread()->isRunning()) {
auto ev = new Event(static_cast<QEvent::Type>(Event::EventType1));
qApp->postEvent(worker, ev, Qt::HighEventPriority);
}
return app.exec();
}