Проблема довольно проста: вы блокируете цикл событий в основном потоке, когда вы заканчиваете wait
. Тем не менее, в то же время цикл события должен получить вызов workerThread->quit()
. Таким образом, вы зашли в тупик.
Простым исправлением является явно quit()
нить до wait()
для него. Вы достигаете этого, фиксируя присущую ему разлому QThread
. Ниже приведена безопасная реализация класса Thread
. Затем вы можете просто уничтожить поток в деструкторе MainWindow
.
Увы, код имеет некоторые антипаттерны и вуду.
Ни workerThread
, ни worker
должны быть указателями. Это преждевременная пессимизация, поскольку вы добавляете дополнительное распределение кучи и дополнительный слой косвенности. Это бессмысленно и заставляет вас делать ручное управление памятью, в котором никто не требует.
threadStopSlot
является потокобезопасным, и нет причин для его вызова из цикла событий рабочего потока. Вы можете назвать это напрямую. Когда вы это сделаете, вам не нужно звонить QCoreApplication::processEvents
в runSlot
. Когда вы это сделаете, вы снова вводите цикл событий, и вдруг все ваши объекты, работающие в этом потоке, подлежат повторному требованию и должны быть проверены как таковые. Плохая идея - не делай этого.
Так как вы, вероятно, хотите, чтобы позволить запустить цикл обработки событий в потоке рабочего, вы должны инвертировать управление: вместо того, чтобы держать контроль в runSlot
, держать его в курсе событий, и есть вызов цикла событий runSlot
несколько раз. Это идиома таймера тайм-аута.
Списки инициаторов дают начало идиоматическому C++. Используй их.
emit
предназначен как префикс, а не как функция. Вы emit fooSignal()
, а не emit(fooSignal())
. Это вопрос стиля, правда, но emit
предназначен для документации. Это предназначено только для потребления человеком, и мы, люди, легче читаем вещи, когда они не завернуты в дополнительный слой круглых скобок.Если вам не нужен аспект документации, вообще не используйте emit
. Сигналы являются регулярными методами с машинной реализацией. Вам не нужно называть их каким-либо особым образом.
Поскольку вы используете Qt 5, вы должны использовать синтаксис connect
с компиляцией. Для этого не требуется компилятор C++ 11 - если вы не используете lambdas.
Возможно, это плохая идея попросить потоки выйти из деструктора окна, поскольку в главном потоке могут возникать другие вещи, поскольку рабочие потоки завершаются - вещи, которые требуют запуска цикла событий ,
Единственный способ разрушения окна - если у него есть атрибут WA_DeleteOnClose
или если вы закончили цикл основного события и разрушили окно при выходе из main()
. Вы должны поймать событие закрытия окна и настроить движение, чтобы окно удалялось только тогда, когда все, что должно быть сделано, сделано.
Вместо вывода идентификатора потока в qDebug()
вы можете выводить сам поток. Вы можете использовать тот факт, что потоки являются объектами, и вы можете дать им читаемые человеком имена. Затем вам не нужно сравнивать идентификаторы потоков или адреса вручную, вы просто читаете имя потока.
Учитывая все вышесказанное, если вы попросили меня написать код, я бы сделал это следующим образом.
Во-первых, функциональность работник может быть абстрагированы в рабочий базе:
#include <QtWidgets>
class WorkerBase : public QObject {
Q_OBJECT
Q_PROPERTY(bool active READ isActive WRITE setActive)
QBasicTimer m_runTimer;
bool m_active;
protected:
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() != m_runTimer.timerId()) return;
work();
}
virtual void workStarted() {}
virtual void work() = 0;
virtual void workEnded() {}
public:
WorkerBase(QObject * parent = 0) : QObject(parent), m_active(false) {
setActive(true);
}
/// This method is thread-safe.
bool isActive() const { return m_active; }
/// This method is thread-safe.
void setActive(bool active) {
QObject source;
QObject::connect(&source, &QObject::destroyed, this, [this,active]{
// The functor is executed in the thread context of this object
if (m_active == active) return;
if (active) {
m_runTimer.start(0, this);
workStarted();
} else {
m_runTimer.stop();
workEnded();
}
m_active = active;
}, thread() ? Qt::QueuedConnection : Qt::DirectConnection);
}
~WorkerBase() {
Q_ASSERT(QThread::currentThread() == thread() || !thread());
setActive(false);
}
};
Затем работник становится простым:
class Worker : public WorkerBase {
Q_OBJECT
int m_runCount;
protected:
void workStarted() Q_DECL_OVERRIDE {
qDebug() << "Starting" << QThread::currentThread();
}
void work() Q_DECL_OVERRIDE {
QThread::msleep(1000);
++ m_runCount;
qDebug() << m_runCount << QThread::currentThread();
}
void workEnded() Q_DECL_OVERRIDE {
qDebug() << "Finishing" << QThread::currentThread();
emit finished();
}
public:
Worker(QObject * parent = 0) : WorkerBase(parent), m_runCount(0) {}
Q_SIGNAL void finished();
};
Наконец, добавим реализацию поточно, и основное окно, которое удерживает локус управления в цикле событий и пропускает цикл до тех пор, пока все не будет готово к разрушению окна:
class Thread : public QThread {
using QThread::run; // final method
public:
Thread(QObject * parent = 0) : QThread(parent) {}
~Thread() { quit(); wait(); }
};
class MainWindow : public QMainWindow {
Q_OBJECT
Worker m_worker;
Thread m_workerThread;
QLabel m_label;
protected:
void closeEvent(QCloseEvent * ev) {
if (m_worker.isActive()) {
m_worker.setActive(false);
ev->ignore();
} else
ev->accept();
}
public:
MainWindow(QWidget * parent = 0) : QMainWindow(parent),
m_label("Hello :)\nClose the window to quit.")
{
setCentralWidget(&m_label);
m_workerThread.setObjectName("m_worker");
m_worker.moveToThread(&m_workerThread);
connect(&m_worker, &Worker::finished, this, &QWidget::close);
m_workerThread.start();
qDebug() << "Main thread:" << QThread::currentThread();
}
~MainWindow() {
qDebug() << __FUNCTION__ << QThread::currentThread();
}
};
int main(int argc, char ** argv)
{
QApplication a(argc, argv);
QThread::currentThread()->setObjectName("main");
MainWindow w;
w.show();
w.setAttribute(Qt::WA_QuitOnClose);
return a.exec();
}
#include "main.moc"
Обратите внимание на заметное отсутствие множества сигналов/слотов, требуемых вашим подходом. Все, что вам нужно, - это когда рабочий объект закончил, вы можете закрыть окно и разорвать все объекты на клочки.
Обратите внимание, что заказ декларации важен. Семантика C++ не является случайной в этом отношении: порядок имеет смысл. Рабочий поток должен быть объявлен после рабочего объекта, так как он будет разрушен в обратном порядке. Таким образом, поток сначала заканчивается и уничтожается. В этот момент m_worker->thread() == nullptr
и вы можете уничтожить работника из любого потока, включая основной поток. Было бы ошибкой, если бы поток работника все еще существовал - тогда вам нужно было бы уничтожить рабочего в своем потоке.
Выход:
Main thread: QThread(0x7fa59b501180, name = "main")
Starting QThread(0x7fff5e053af8, name = "m_worker")
1 QThread(0x7fff5e053af8, name = "m_worker")
2 QThread(0x7fff5e053af8, name = "m_worker")
3 QThread(0x7fff5e053af8, name = "m_worker")
Finishing QThread(0x7fff5e053af8, name = "m_worker")
~MainWindow QThread(0x7fa59b501180, name = "main")
Как кварт 5.1.1, я также проходил под другой стандарт кодирования, чем большинство может быть использовано для. Однако это выходит за рамки моего вопроса. – pdel