Конкурентное выполнение
Функция QtConcurrent::run() выполняет функцию в отдельном потоке. Значение возврата функции доступно через API QFuture.
QtConcurrent::run() — это перегруженный метод. Вы можете рассматривать эти перегрузки как немного отличающиеся режимы. В режиме основного режима функция, переданная в QtConcurrent::run(), может сообщить своему вызывающему объекту только одно значение результата вычисления. В режиме выполнения с обещанием функция, переданная в QtConcurrent::run(), может использовать дополнительный API QPromise, который позволяет сообщать о нескольких результатах, отображать прогресс, приостанавливать вычисление по запросу вызывающего объекта или останавливать вычисление по требованию вызывающего объекта.
Эта функция является частью фреймворка Qt Concurrent.
Конкурентное выполнение (основной режим)
Функция, переданная в QtConcurrent::run(), может сообщить результат через возвращаемое значение.
Выполнение функции в отдельном потоке
Для выполнения функции в другом потоке используйте QtConcurrent::run():
extern void aFunction(); QFuture<void> future = QtConcurrent::run(aFunction);
Это запустит aFunction в отдельном потоке, полученном из стандартного QThreadPool. Вы можете использовать классы QFuture и QFutureWatcher для отслеживания состояния функции.
Для использования отдельного пула потоков вы можете передать QThreadPool в качестве первого аргумента:
extern void aFunction(); QThreadPool pool; QFuture<void> future = QtConcurrent::run(&pool, aFunction);
Передача аргументов функции
Передача аргументов функции выполняется путем добавления их в вызов QtConcurrent::run() сразу после имени функции. Например:
extern void aFunctionWithArguments(int arg1, double arg2, const QString &string); int integer = ...; double floatingPoint = ...; QString string = ...; QFuture<void> future = QtConcurrent::run(aFunctionWithArguments, integer, floatingPoint, string);
Копия каждого аргумента создаётся в момент вызова QtConcurrent::run(), а эти значения передаются в поток при его запуске. Изменения, внесенные в аргументы после вызова QtConcurrent::run(), не видны потоку.
Обратите внимание, что QtConcurrent::run не поддерживает непосредственный вызов перегруженных функций. Например, следующий код не будет компилироваться:
void foo(int arg); void foo(int arg1, int arg2); ... QFuture<void> future = QtConcurrent::run(foo, 42);
Самым простым решением является вызов перегруженной функции через лямбда-выражение:
QFuture<void> future = QtConcurrent::run([] { foo(42); });Или вы можете указать компилятору, какую перегрузку выбрать, используя static_cast:
QFuture<void> future = QtConcurrent::run(static_cast<void(*)(int)>(foo), 42);
Или qOverload:
QFuture<void> future = QtConcurrent::run(qOverload<int>(foo), 42);
Возвращение значений из функции
Любое возвращаемое значение функции доступно через QFuture:
extern QString functionReturningAString(); QFuture<QString> future = QtConcurrent::run(functionReturningAString); ... QString result = future.result();
Как указано выше, передача аргументов выполняется следующим образом:
extern QString someFunction(const QByteArray &input); QByteArray bytearray = ...; QFuture<QString> future = QtConcurrent::run(someFunction, bytearray); ... QString result = future.result();
Обратите внимание, что функция QFuture::result() блокирует выполнение и ожидает, пока результат не станет доступным. Используйте QFutureWatcher, чтобы получить уведомление о завершении выполнения функции и доступности результата.
Дополнительные возможности API
Использование методов-членов
QtConcurrent::run() также принимает указатели на методы-члены. Первый аргумент должен быть либо константной ссылкой, либо указателем на экземпляр класса. Передача по константной ссылке полезна при вызове константных методов-членов; передача по указателю полезна для вызова неконстантных методов-членов, которые изменяют экземпляр.
Например, вызов QByteArray::split() (константный метод-член) в отдельном потоке выполняется следующим образом:
// call 'QList<QByteArray> QByteArray::split(char sep) const' in a separate thread QByteArray bytearray = "hello world"; QFuture<QList<QByteArray> > future = QtConcurrent::run(&QByteArray::split, bytearray, ' '); ... QList<QByteArray> result = future.result();
Вызов неконстантного метода-члена выполняется следующим образом:
// call 'void QImage::invertPixels(InvertMode mode)' in a separate thread QImage image = ...; QFuture<void> future = QtConcurrent::run(&QImage::invertPixels, &image, QImage::InvertRgba); ... future.waitForFinished(); // At this point, the pixels in 'image' have been inverted
Использование лямбда-функций
Вызов лямбда-функции выполняется следующим образом:
QFuture<void> future = QtConcurrent::run([=]() {
// Code in this block will run in another thread
});
...Вызов функции, изменяющей объект, переданный по ссылке, выполняется следующим образом:
static void addOne(int &n) { ++n; }
...
int n = 42;
QtConcurrent::run(&addOne, std::ref(n)).waitForFinished(); // n == 43Использование вызываемого объекта выполняется следующим образом:
struct TestClass
{
void operator()(int s1) { s = s1; }
int s = 42;
};
...
TestClass o;
// Modify original object
QtConcurrent::run(std::ref(o), 15).waitForFinished(); // o.s == 15
// Modify a copy of the original object
QtConcurrent::run(o, 42).waitForFinished(); // o.s == 15
// Use a temporary object
QtConcurrent::run(TestClass(), 42).waitForFinished();
// Ill-formed
QtConcurrent::run(&o, 42).waitForFinished(); // compilation errorКонкурентное выполнение с обещанием
Режим выполнения с обещанием предоставляет больший контроль над выполняемой задачей по сравнению с основным режимом QtConcurrent::run(). Он позволяет отображать прогресс выполнения задачи, сообщать о нескольких результатах, приостанавливать выполнение, если это запрошено, или отменять задачу по требованию вызывающего объекта.
Обязательный аргумент QPromise
Функция, переданная в QtConcurrent::run() в режиме выполнения с обещанием, должна иметь дополнительный аргумент типа QPromise<T> &, где T — тип результата вычисления (он должен соответствовать типу T из QFuture<T>, возвращаемому QtConcurrent::runWithPromise()), например:
extern void aFunction(QPromise<void> &promise); QFuture<void> future = QtConcurrent::run(aFunction);
Аргумент promise инициализируется внутри функции QtConcurrent::run(), и его ссылка передаётся вызываемому aFunction, поэтому пользователю не нужно его инициализировать самостоятельно, ни передавать явно при вызове QtConcurrent::runWithPromise().
Дополнительный аргумент типа QPromise всегда должен появляться в качестве первого аргумента в списке аргументов функции, например:
extern void aFunction(QPromise<void> &promise, int arg1, const QString &arg2); int integer = ...; QString string = ...; QFuture<void> future = QtConcurrent::run(aFunction, integer, string);
Отчёт о результатах
В отличие от основного режима QtConcurrent::run(), функция, переданная в QtConcurrent::run() в режиме выполнения с обещанием, должна всегда возвращать тип void. Сообщения о результатах выполняются через дополнительный аргумент типа QPromise. Это также позволяет сообщать о нескольких результатах, например:
void helloWorldFunction(QPromise<QString> &promise)
{
promise.addResult("Hello");
promise.addResult("world");
}
QFuture<QString> future = QtConcurrent::run(helloWorldFunction);
...
QList<QString> results = future.results();Примечание: Нет необходимости вызывать QPromise::start() и QPromise::finish() для указания начала и конца вычислений (как обычно делается при использовании QPromise). QtConcurrent::run() всегда вызовет их до начала и после завершения выполнения.
Приостановка и отмена выполнения
API QPromise также позволяет приостанавливать и отменять вычисления по запросу:
void aFunction(QPromise<int> &promise)
{
for (int i = 0; i < 100; ++i) {
promise.suspendIfRequested();
if (promise.isCanceled())
return;
// computes the next result, may be time consuming like 1 second
const int res = ... ;
promise.addResult(res);
}
}
QFuture<int> future = QtConcurrent::run(aFunction);
... // user pressed a pause button after 10 seconds
future.suspend();
... // user pressed a resume button after 10 seconds
future.resume();
... // user pressed a cancel button after 10 seconds
future.cancel();Вызов future.suspend() запрашивает выполняемую задачу приостановить своё выполнение. После вызова этого метода, выполняемая задача приостановится после следующего вызова promise.suspendIfRequested() в цикле итерации. В этом случае выполняемая задача заблокируется при вызове promise.suspendIfRequested(). Блокированный вызов разблокируется после вызова future.resume(). Обратите внимание, что функция suspendIfRequested() использует условие ожидания для разблокирования, поэтому поток выполнения переходит в состояние бездействия вместо того, чтобы тратить ресурсы на периодическую проверку наличия запроса на возобновление от потока вызывающего объекта.
Вызов future.cancel() в последней строке приводит к тому, что следующий вызов promise.isCanceled() вернёт true и aFunction вернётся сразу же без дальнейшего сообщения о результате.
Примечание: Нет необходимости вызывать QPromise::finish() для остановки вычислений после отмены (как обычно делается при использовании QPromise). QtConcurrent::run() всегда вызовет его после завершения выполнения.
Отчёт о прогрессе
Также можно отслеживать прогресс задачи независимо от сообщения о результате, например:
void aFunction(QPromise<int> &promise)
{
promise.setProgressRange(0, 100);
int result = 0;
for (int i = 0; i < 100; ++i) {
// computes some part of the task
const int part = ... ;
result += part;
promise.setProgressValue(i);
}
promise.addResult(result);
}
QFutureWatcher<int> watcher;
QObject::connect(&watcher, &QFutureWatcher::progressValueChanged, [](int progress){
... ; // update GUI with a progress
qDebug() << "current progress:" << progress;
});
watcher.setFuture(QtConcurrent::run(aFunction));Вызывающий объект устанавливает QFutureWatcher для QFuture возвращаемого QtConcurrent::run() для подключения к его сигналу progressValueChanged() и обновления, например, графического пользовательского интерфейса соответственно.
Вызов функций с перегруженным оператором()
По умолчанию QtConcurrent::run() не поддерживает функторы с перегруженным оператором() в режиме выполнения с обещанием. В случае перегруженных функторов пользователь должен явно указать тип результата в качестве параметра шаблона, передаваемого в QtConcurrent::run(), например:
struct Functor {
void operator()(QPromise<int> &) { }
void operator()(QPromise<double> &) { }
};
Functor f;
run<double>(f); // this will select the 2nd overload
// run(f); // error, both candidate overloads potentially match
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-6.1/qtconcurrentrun.html