Spec-Zone.ru › Qt

Конкурентный запуск

Функция 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.2/qtconcurrentrun.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API