QFuture Class

template <typename T> class QFuture

The QFuture class represents the result of an asynchronous computation. More...

Header: #include <QFuture>
CMake: find_package(Qt6 REQUIRED COMPONENTS Core)
target_link_libraries(mytarget PRIVATE Qt6::Core)
qmake: QT += core

Note: All functions in this class are thread-safe with the following exceptions:

Public Types

Detailed Description

QFuture allows threads to be synchronized against one or more results which will be ready at a later point in time. The result can be of any type that has default, copy and possibly move constructors. If a result is not available at the time of calling the result(), resultAt(), results() and takeResult() functions, QFuture will wait until the result becomes available. You can use the isResultReadyAt() function to determine if a result is ready or not. For QFuture objects that report more than one result, the resultCount() function returns the number of continuous results. This means that it is always safe to iterate through the results from 0 to resultCount(). takeResult() invalidates a future, and any subsequent attempt to access result or results from the future leads to undefined behavior. isValid() tells you if results can be accessed.

QFuture provides a Java-style iterator (QFutureIterator) and an STL-style iterator (QFuture::const_iterator). Using these iterators is another way to access results in the future.

If the result of one asynchronous computation needs to be passed to another, QFuture provides a convenient way of chaining multiple sequential computations using then(). onCanceled() can be used for adding a handler to be called if the QFuture is canceled. Additionally, onFailed() can be used to handle any failures that occurred in the chain. Note that QFuture relies on exceptions for the error handling. If using exceptions is not an option, you can still indicate the error state of QFuture, by making the error type part of the QFuture type. For example, you can use std::variant, std::any or similar for keeping the result or failure or make your custom type.

The example below demonstrates how the error handling can be done without using exceptions. Let's say we want to send a network request to obtain a large file from a network location. Then we want to write it to the file system and return its location in case of a success. Both of these operations may fail with different errors. So, we use std::variant to keep the result or error:

 using NetworkReply = std::variant<QByteArray, QNetworkReply::NetworkError>;

 enum class IOError { FailedToRead, FailedToWrite };
 using IOResult = std::variant<QString, IOError>;

And we combine the two operations using then():

 QFuture<IOResult> future = QtConcurrent::run([url] {
         ...
         return NetworkReply(QNetworkReply::TimeoutError);
 }).then([](NetworkReply reply) {
     if (auto error = std::get_if<QNetworkReply::NetworkError>(&reply))
         return IOResult(IOError::FailedToRead);

     auto data = std::get_if<QByteArray>(&reply);
     // try to write *data and return IOError::FailedToWrite on failure
     ...
 });

 auto result = future.result();
 if (auto filePath = std::get_if<QString>(&result)) {
     // do something with *filePath
 else
     // process the error

It's possible to chain multiple continuations and handlers in any order. For example:

 QFuture<int> testFuture = ...;
 auto resultFuture = testFuture.then([](int res) {
     // Block 1
 }).onCanceled([] {
     // Block 2
 }).onFailed([] {
     // Block 3
 }).then([] {
     // Block 4
 }).onFailed([] {
     // Block 5
 }).onCanceled([] {
     // Block 6
 });

Depending on the state of testFuture (canceled, has exception or has a result), the next onCanceled(), onFailed() or then() will be called. So if testFuture is successfully fulfilled, Block 1 will be called. If it succeeds as well, the next then() (Block 4) is called. If testFuture gets canceled or fails with an exception, either Block 2 or Block 3 will be called respectively. The next then() will be called afterwards, and the story repeats.

Note: If Block 2 is invoked and throws an exception, the following onFailed() (Block 3) will handle it. If the order of onFailed() and onCanceled() were reversed, the exception state would propagate to the next continuations and eventually would be caught in Block 5.

In the next example the first onCanceled() (Block 2) is removed:

 QFuture<int> testFuture = ...;
 auto resultFuture = testFuture.then([](int res) {
     // Block 1
 }).onFailed([] {
     // Block 3
 }).then([] {
     // Block 4
 }).onFailed([] {
     // Block 5
 }).onCanceled([] {
     // Block 6
 });

If testFuture gets canceled, its state is propagated to the next then(), which will be also canceled. So in this case Block 6 will be called.

The future can have only one continuation. Consider the following example:

 QPromise<int> p;

 QFuture<int> f1 = p.future();
 f1.then([](int) { qDebug("first"); });

 QFuture<int> f2 = p.future();
 f2.then([](int) { qDebug("second"); });

 p.start();
 p.addResult(42);
 p.finish();

In this case f1 and f2 are effectively the same QFuture object, as they share the same internal state. As a result, calling then on f2 will overwrite the continuation specified for f1. So, only "second" will be printed when this code is executed.

QFuture also offers ways to interact with a running computation. For instance, the computation can be canceled with the cancel() function. To suspend or resume the computation, use the setSuspended() function or one of the suspend(), resume(), or toggleSuspended() convenience functions. Be aware that not all running asynchronous computations can be canceled or suspended. For example, the future returned by QtConcurrent::run() cannot be canceled; but the future returned by QtConcurrent::mappedReduced() can.

Progress information is provided by the progressValue(), progressMinimum(), progressMaximum(), and progressText() functions. The waitForFinished() function causes the calling thread to block and wait for the computation to finish, ensuring that all results are available.

The state of the computation represented by a QFuture can be queried using the isCanceled(), isStarted(), isFinished(), isRunning(), isSuspending() or isSuspended() functions.

QFuture<void> is specialized to not contain any of the result fetching functions. Any QFuture<T> can be assigned or copied into a QFuture<void> as well. This is useful if only status or progress information is needed - not the actual result data.

To interact with running tasks using signals and slots, use QFutureWatcher.

You can also use QtFuture::connect() to connect signals to a QFuture object which will be resolved when a signal is emitted. This allows working with signals like with QFuture objects. For example, if you combine it with then(), you can attach multiple continuations to a signal, which are invoked in the same thread or a new thread.

The QtFuture::whenAll() and QtFuture::whenAny() functions can be used to combine several futures and track when the last or first of them completes.

A ready QFuture object with a value or a QFuture object holding exception can be created using convenience functions QtFuture::makeReadyVoidFuture(), QtFuture::makeReadyValueFuture(), QtFuture::makeReadyRangeFuture(), and QtFuture::makeExceptionalFuture().

Note: Some APIs (see QFuture::then() or various QtConcurrent method overloads) allow scheduling the computation to a specific thread pool. However, QFuture implements a work-stealing algorithm to prevent deadlocks and optimize thread usage. As a result, computations can be executed directly in the thread which requests the QFuture's result.

Note: To start a computation and store results in a QFuture, use QPromise or one of the APIs in the Qt Concurrent framework.

See also QPromise, QtFuture::connect(), QtFuture::makeReadyVoidFuture(), QtFuture::makeReadyValueFuture(), QtFuture::makeReadyRangeFuture(), QtFuture::makeExceptionalFuture(), QFutureWatcher, and Qt Concurrent.

Member Type Documentation

QFuture::ConstIterator

Qt-style synonym for QFuture::const_iterator.