并发运行

QtConcurrent::run() 函数在单独的线程中运行一个函数。该函数的返回值通过 QFuture API 提供使用。

QtConcurrent::run() 是一个重载方法。你可以将这些重载视为略有不同的 模式。在 基本模式 中,传递给 QtConcurrent::run() 的函数只能向其调用者报告单个计算结果。在 带承诺运行模式 中,传递给 QtConcurrent::run() 的函数可以利用额外的 QPromise API,这使得可以报告多个结果、报告进度、在调用者请求时挂起计算,或者在调用者要求停止计算。

此函数是 Qt 并发框架的一部分。

并发运行(基本模式)

传递给 QtConcurrent::run() 的函数可能通过其返回值来报告结果。

在单独的线程中运行函数

要在其他线程中运行一个函数,请使用 QtConcurrent::run()

extern void aFunction();
QFuture<void> future = QtConcurrent::run(aFunction);

这将使用从默认的QThreadPool 获取的单独线程运行 aFunction。您可以使用 QFutureQFutureWatcher 类来监控函数的状态。

要使用专用的线程池,可以将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);

最简单的解决方案是通过 lambda 调用重载函数

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();

如果您不需要结果(例如,因为函数返回 void),使用接受函数对象的 QThreadPool::start() 重载会更高效。

如上所述,传递参数是这样进行的

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

使用Lambda函数

调用Lambda函数的方式如下

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

使用Promise并发运行

使用Promise模式比QtConcurrent::run()的基本模式提供了更多的运行任务控制。它允许报告运行任务的进度,报告多个结果,如果请求的话可以暂停执行,或者根据调用者的要求取消任务。

必选的QPromise参数

传递给QtConcurrent::run()的函数在使用Promise模式时预期有一个额外的参数,其类型为QPromise<T> &,其中是计算结果类型(它应该与QtConcurrent::run()返回的QFuture<T>类型匹配),例如:

extern void aFunction(QPromise<void> &promise);
QFuture<void> future = QtConcurrent::run(aFunction);

在QtConcurrent::run()函数内部实例化了promise参数,并将它的引用传递给被调用的aFunction,因此用户不需要实例化它,也不需要在调用QtConcurrent::run()时显式传递它。

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()的基本模式相反,在使用Promise模式的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()将在开始和完成后永远调用它们。

暂停和取消执行

QPromise API还启用根据需求暂停和取消计算。

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));

调用者为 QtConcurrent::run() 返回的 QFuture 安装 QFutureWatcher,以便连接到其 progressValueChanged() 信号并相应地更新,例如图形用户界面。

调用重载了 operator()() 的函数

默认情况下,QtConcurrent::run() 在 Run With Promise 模式下不支持具有重载 operator()() 的函数对象。在重载的函数对象的情况下,用户需要显式指定结果类型作为模板参数传递给 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

© 2024 Qt 公司。本文档中包含的文档贡献属于其各自所有者的版权。本提供的文档根据自由软件基金会发布的 GNU 自由文档许可证版本 1.3 的条款进行许可。Qt 及其相关标志是芬兰的 Qt 公司及其在其他国家和地区注册的商标。所有其他商标均为其各自所有者的财产。