Введение
Модуль QtConcurrent предоставляет высокоуровневый API для параллельных вычислений, скрывающий детали создания и синхронизации потоков. Вместо явного создания QThread или QRunnable достаточно вызвать функцию вроде QtConcurrent::run(), передав ей обычную функцию или лямбду — она будет выполнена в пуле потоков, а результат можно получить через объект QFuture. В статье разберём базовые сценарии использования QFuture и QtConcurrent::run.
Концепция
QtConcurrent::run() помещает переданный вызываемый объект в QThreadPool и немедленно возвращает QFuture<T>, который представляет собой «обещание» будущего результата. У QFuture есть методы для блокирующего ожидания (result(), waitForFinished()) и неблокирующей проверки состояния (isFinished(), isRunning()). Для реакции на завершение без блокирования потока используется QFutureWatcher, который испускает сигнал finished() при готовности результата — это позволяет интегрировать асинхронные вычисления в событийную модель Qt без блокировки GUI-потока.
Пример кода
#include <QCoreApplication>
#include <QtConcurrent/QtConcurrent>
#include <QFuture>
#include <QFutureWatcher>
#include <QDebug>
long long computeFactorial(int n)
{
long long result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// Вариант 1: блокирующее ожидание результата
QFuture<long long> future = QtConcurrent::run(computeFactorial, 15);
qDebug() << "Вычисление запущено в фоне...";
long long blockingResult = future.result(); // блокирует текущий поток
qDebug() << "15! =" << blockingResult;
// Вариант 2: неблокирующее ожидание через QFutureWatcher
auto *watcher = new QFutureWatcher<long long>;
QObject::connect(watcher, &QFutureWatcher<long long>::finished, [watcher, &app]() {
qDebug() << "20! =" << watcher->result();
watcher->deleteLater();
app.quit();
});
QFuture<long long> asyncFuture = QtConcurrent::run(computeFactorial, 20);
watcher->setFuture(asyncFuture);
return app.exec();
}
Пояснения к коду
Первый вариант показывает простейший, но блокирующий способ получить результат: future.result() дождётся завершения вычисления в текущем потоке. Это уместно в консольных утилитах, но недопустимо в GUI-потоке, так как заблокирует интерфейс на время вычисления.
Второй вариант — правильный паттерн для интерфейсных приложений: QFutureWatcher подключается к QFuture через setFuture(), и когда вычисление завершается в фоновом потоке, сигнал finished() доставляется в поток владельца watcher (как правило, основной поток) через стандартный механизм межпотоковых сигналов — результат можно безопасно забрать через watcher->result() без блокировки.
Подводные камни
future.result()в GUI-потоке замораживает интерфейс ровно так же, как синхронный вызов — использованиеQFutureсамо по себе не освобождает от необходимости думать об асинхронности взаимодействия с результатом.- Утечка
QFutureWatcher, если не вызватьdeleteLater()после получения результата — в примере это сделано внутри лямбдыfinished, но при копировании паттерна в реальный код об этом легко забыть. - QtConcurrent::run использует глобальный QThreadPool по умолчанию — при большом числе параллельных вызовов из разных частей приложения они конкурируют за общий пул потоков, как и при прямом использовании
QRunnable. - Исключения внутри функции, переданной в
QtConcurrent::run, перехватываются и сохраняются вQFuture, и будут выброшены повторно при вызовеresult()— если не обрабатывать их черезtry/catchвокругresult(), необработанное исключение приведёт к завершению приложения.