Небольшая заметка. Часто забываю некоторые нюансы…
О том, что вызов унаследованного от QThread метода run считается некорректным, думаю, писать не стоит — это уже давно разъяснено.
Отмечу здесь некоторые нюансы работы с тредами в Qt, которые мне были нужны и которые приходилось собирать по кускам из сети:
- Отображение прогресса работы треда в QProgressBar
- Принудительное завершение треда
- Возврат из треда результата типа какого-нибудь «крафтового» класса
Итак, как известно, помещение задачи в отдельный тред и её запуск в общем виде выглядит так (например, в функции по нажатию кнопки на главном окне):
//создаем worker - объект от QObject, инкапсулирующий нужный алгоритм,
ThreadWorker *worker = new ThreadWorker();
//создаем объект отдельного потока QThread
thread = new QThread();
//помещаем worker в созданный поток, используя функцию QObject::moveToThread()
worker->moveToThread(thread);
//присоединяем сигналы к слотам
//...об этом позже
//запускаем поток!
thread->start();
Для выполнения задачи в отдельном треде нужно завернуть её в функцию — скажем, process() — отдельного worker-класса, унаследованного от QObject с подключением мета-объектного компилятора MOC путём указания макроса Q_OBJECT:
class ThreadWorker : public QObject
{
Q_OBJECT
public slots:
void process();
Как видно, главная функция сразу обозначена как слот, который будет вызван по сигналу. Этим сигналом к запуску функции ThreadWorker::process() будет запуск треда QThread::start(). Таким образом, после создания объекта треда и помещения в него экземпляра worker’а, подключаем сигнал к слоту:
connect(thread, SIGNAL(started()), worker, SLOT(process()));
Мы хотим получить не только результат работы функции ThreadWorker::process(), но и отображать прогресс её выполнения. А также результат мы хотим получить не в виде стандартного типа, а некоего определённого нами класса ThreadResultType. Таким образом, наш worker должен генерировать сигналы:
class ThreadWorker : public QObject
{
Q_OBJECT
public slots:
void process();
signals:
void finished();
void result(ThreadResultType result);
void progress(int value);
Сигнал finished() будет привязан к слоту треда deleteLater() для автоматического удаления его объекта. Сигнал progress(int value) привяжем к слоту setValue(int) имеющегося QProgressBar’а, а сигнал result(ThreadResultType result) мы привяжем к созданной нами функции главного окна MainWindow::getThreadResult(ThreadResultType result);
Для того, чтобы можно было передавать свои типы данных через механизм слотов, их нужно зарегистрировать в функции main():
qRegisterMetaType<ThreadResultType>("ThreadResultType");
При этом класс, используемый для передачи данных посредством слотов, должен обладать следующими свойствами: иметь конструктор по умолчанию, иметь конструктор копирования и деструктор:
class ThreadResultType
{
private:
int value;
QString message;
public:
ThreadResultType();
ThreadResultType(const ThreadResultType ©From);
ThreadResultType(int val, const QString &msg);
int getValue() const;
QString getMessage() const;
void setValue(int value);
void setMessage(const QString &message);
};
Итак, полный набор соединений сигнал-слот, который мы располагаем после создания объекта треда и перед его запуском:
//worker испускает сигнал finished в слот завершения потока
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
//сигнал запуска потока посылаем в функцию worker'а, выполняющую нужную нам работу
connect(thread, SIGNAL(started()), worker, SLOT(process()));
//сигнал worker'а о состоянии прогресса посылаем в слот изменения ProgressBar'а
connect(worker, SIGNAL(progress(int)), ui->progressBar, SLOT(setValue(int)) );
//сигнал worker'а с результатом созданного типа посылаем в слот приема результата
connect(worker, SIGNAL(result(ThreadResultType)), this, SLOT(getThreadResult(ThreadResultType)));
//worker и thread сами удаляют друг друга по завершению работы по сигналу finished()
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
Прелесть параллельно выполняемого кода ещё и в том, что его можно легко прервать без всяких флагов и прочих приседаний в основном процессе. Для этого нужно вызвать метод QThread.requestInterruption(), например, по нажатию кнопки на основной форме в главном потоке:
thread->requestInterruption();
Тогда нужно в цикле работы worker’а ThreadWorker::process() отлавливать запрос на остановку треда QThread::currentThread()->isInterruptionRequested():
bool runThread = true;
int loop = 0;
while( (loop < maxLoop) && runThread )
{
counter++;
loop++;
QThread::usleep(100000);
emit progress(counter);
runThread = !QThread::currentThread()->isInterruptionRequested();
}
После завершения рабочего цикла отправляем результат путем генерации сигналов результата и завершения:
ThreadResultType resultData;
resultData.setValue(counter);
resultData.setMessage(QString("Счетчик равен %1.").arg(counter));
emit result(resultData);
emit finished();