并发任务

QtConcurrent::task提供了在单独线程中运行任务的另一种接口。函数的返回值通过QFuture API提供。

如果您只想在单独的线程中运行一个函数而不调整任何参数,则使用QtConcurrent::run,因为它允许您编写更少的代码。QtConcurrent::task是为了那些需要执行额外配置步骤的情况而设计的。

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

流畅接口

QtConcurrent::task返回一个名为QtConcurrent::QTaskBuilder的辅助类实例。通常,您不需要手动创建此类实例。QtConcurrent::QTaskBuilder提供了一种通过链式方式调整不同任务参数的接口。这种方法被称为流畅接口

您只需设置所需的参数,然后启动一个任务。为了最终确定任务的配置,您必须调用QtConcurrent::QTaskBuilder::spawn。此函数是非阻塞的(即立即返回一个将来对象),但保证任务不会立即启动。您可以使用QFutureQFutureWatcher类来监控任务的状态。

下面有更多示例和说明。

在单独的线程中运行任务

要在线程中运行一个函数,请使用QtConcurrent::QTaskBuilder::spawn

QtConcurrent::task([]{ qDebug("Hello, world!"); }).spawn();

这将运行一个在默认的QThreadPool中获得的单独线程的lambda函数。

传递参数到任务

通过将它们传递给QtConcurrent::QTaskBuilder::withArguments来调用带有参数的函数

auto task = [](const QString &s){ qDebug() << ("Hello, " + s); };
QtConcurrent::task(std::move(task))
    .withArguments("world!")
    .spawn();

QtConcurrent::QTaskBuilder::withArguments被调用时,将制作每个参数的副本,并且当线程开始执行任务时传递这些值。调用QtConcurrent::QTaskBuilder::withArguments之后的参数更改对线程不可见。

如果您想运行接受引用参数的函数,应使用std::ref/cref辅助函数。这些函数在传递参数的周围创建薄包装器

QString s("Hello, ");
QtConcurrent::task([](QString &s){ s.append("world!"); })
    .withArguments(std::ref(s))
    .spawn();

请确保所有包装的持久性足够。如果跳出包装器后任务仍然存在,则可能会出现未定义的行为。

从任务中返回值

您可以使用 QFuture API 获取任务的执行结果

auto future = QtConcurrent::task([]{ return 42; }).spawn();
auto result = future.result(); // result == 42

请注意,QFuture::result() 是一个阻塞调用,它等待结果可用。使用 QFutureWatcher 在任务执行完成且结果可用时获取通知。

如果您想要将结果传递给另一个异步任务,可以使用 QFuture::then() 来创建一个依赖任务的链。有关详细信息,请参阅 QFuture 文档。

其他 API 特性

使用不同类型的可调用对象

严格来说,可以使用满足以下条件的任何类型任务和参数

std::is_invocable_v<std::decay_t<Task>, std::decay_t<Args>...>

您可以使用一个自由函数

QVariant value(42);
auto result = QtConcurrent::task([](const QVariant &var){return qvariant_cast<int>(var);})
                  .withArguments(value)
                  .spawn()
                  .result(); // result == 42

您可以使用一个成员函数

QString result("Hello, world!");

QtConcurrent::task(&QString::chop)
    .withArguments(&result, 8)
    .spawn()
    .waitForFinished(); // result == "Hello"

您可以使用一个具有 operator() 的可调用对象

auto result = QtConcurrent::task(std::plus<int>())
                  .withArguments(40, 2)
                  .spawn()
                  .result() // result == 42

如果您想使用现有的可调用对象,您需要将其复制/移动到 QtConcurrent::task 或使用 std::ref/cref 进行包装

struct CallableWithState
{
    void operator()(int newState) { state = newState; }

    // ...
};

// ...

CallableWithState object;

QtConcurrent::task(std::ref(object))
   .withArguments(42)
   .spawn()
   .waitForFinished(); // The object's state is set to 42

使用自定义线程池

您可以指定一个自定义线程池

QThreadPool pool;
QtConcurrent::task([]{ return 42; }).onThreadPool(pool).spawn();

为任务设置优先级

您可以设置任务的优先级

QtConcurrent::task([]{ return 42; }).withPriority(10).spawn();

如果您不需要未来对象,可以调用 QtConcurrent::QTaskBuilder::spawn(QtConcurrent::FutureResult::Ignore)

QtConcurrent::task([]{ qDebug("Hello, world!"); }).spawn(FutureResult::Ignore);

您可以通过在函数内定义一个额外的具有 QPromise<T> & 类型参数的参数来访问与任务关联的承诺对象。此额外参数必须传递给函数的第一个参数,并且类似于在 Concurrent Run With Promise 模式下,该函数预期返回 void 类型。结果报告通过 QPromise API 完成

void increment(QPromise<int> &promise, int i)
{
    promise.addResult(i + 1);
}

int result = QtConcurrent::task(&increment).withArguments(10).spawn().result(); // result == 11

© 2024 Qt 公司有限公司。此处包含的文档贡献者是各自所有者的版权。此处提供的文档根据 GNU 自由文档许可证版本 1.3 的条款发布,该许可证由自由软件基金会发布。Qt 及其相应标志是芬兰以及/或其他国家的 Qt 公司的商标。所有其他商标均为各自所有者的财产。