Ещё немного о QThread

Небольшая заметка. Часто забываю некоторые нюансы…

О том, что вызов унаследованного от 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 &copyFrom);
    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();
Share

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *