Asyncfuture Versions Save

Use QFuture like a Promise object

v0.4.1

6 years ago

AsyncFuture 0.4.1 Release

Changes

  1. Subscribe Callback
  • It is now supported to catch an exception. The future will be cancelled.
auto future = observe(input).subscribe([]() {
	throw QException();
}).future(); // It will be cancelled.
  1. Combinator

It will set progressValue and progressMaxim of its contained future.

        auto d1 = timeout(50);
        auto d2 = timeout(60);
        auto d3 = timeout(30);

        auto combinator = combine();

        combinator << d1 << d2 << d3;

        auto future = combinator.future();
        QCOMPARE(future.progressValue(), 0);
        QCOMPARE(future.progressMaximum(), 3);

        await(future);

        QCOMPARE(future.progressValue(), 3);
        QCOMPARE(future.progressMaximum(), 3);
  1. Merged #8 - fix noisy gcc 7.3 shadow warning

Bug Fix

  1. Not able to take rvalue reference type in subscribe callback.

  2. Complete(QFuture<QFuture<T>>) is a blocked call.

v0.4

6 years ago

AsyncFuture 0.4 Release Notes

New Features

1) Improved Compilation Error Message on invalid callback

The compilation error related to the template in C++ is known to be giant and misleading. It is prioritized to inform you where the error occurs in the template declaration, which is not written by you and you have no knowledge about the implementation.

AsyncFuture v0.4 inserted many static_asserts in the subscribe()/context() to capture error in the early stage. It could reduce the no. of error messages and tell users what is wrong actually in their code.

Error Messages:

  1. Observe a QFuture but your callback contains an input argument
  2. Callback function should not take more than 1 argument
  3. The callback function is not callable. The input argument doesn't match with the observing QFuture type

2) Future Object Tracking - The advanced usage with QtConcurrent::mapped

The deferred object is now supported to track the status of another future object. It will synchronize the progressValue / progressMinimium / progressMaximium and status of the tracking object. (e.g started signal)

For example:


        auto defer = AsyncFuture::deferred<QImage>();

        QFuture<QImage> mapped = QtConcurrent::mapped(files, readImage);

        defer.complete(mapped); // defer.future() will be a mirror of `mapped`. The `progressValue` will be changed and it will emit "started" signal via QFutureWatcher

A practical use-case


QFuture<QImage> readImagesFromFolder(const QString& folder) {

    auto worker = [=]() {
        // Read files from a directory in a thread
        QStringList files = findImageFiles(folder);

        // Concurrent image reader
        return QtConcurrent::mapped(files, readImage);
    };

    auto future = QtConcurrent::run(worker); // The type of future is QFuture<QFuture<QImage>>

    auto defer = AsyncFuture::deferred<QImage>();

    // defer object track the input future. It will emit "started" and `progressValueChanged` according to the status of the future of "QtConcurrent::mapped"
    defer.complete(future);
    return defer.future();
}

In the example code above, the future returned by defer.future() is supposed to be a mirror of the result of QtConcurrent::mapped. However, the linkage is not estimated in the beginning until the worker functions start QtConcurrent::mapped

In case it needs to track the status of a future object but it won’t complete automatically. It may use track() function

3) Support mutable lambda function

In AsyncFuture 0.3, the following is invalid because the deferred object is marked as const

auto defer = AsyncFuture::deferred<void>();
QtConcurrent::run([=]() {
  defer.complete(); // Compilation error
});

Mutable lambda function is supported in v4.0, so it could be solved by marking the lambda function as mutable:

auto defer = AsyncFuture::deferred<void>();
QtConcurrent::run([=]() mutable {
  defer.complete();  
});

New API

AsyncFuture::observe(QFuture<QFuture<T>> future)

This function creates an Observable<T> object which provides an interface for observing the input future. That is designed to handle the following use-case:

QFuture<QImage> readImagesFromFolder(const QString& folder) {

    auto worker = [=]() {
        // Read files from a directory in a thread
        QStringList files = findImageFiles(folder);

        // Concurrent image reader
        return QtConcurrent::mapped(files, readImage);
    };

    auto future = QtConcurrent::run(worker); // The type of future is QFuture<QFuture<QImage>>

    auto defer = AsyncFuture::deferred<QImage>();

    // defer object track the input future. It will emit "started" and `progressValueChanged` according to the status of the future of "QtConcurrent::mapped"
    defer.complete(future);
    return defer.future();
}

See Observable<T>

AsyncFuture::observe(object, SIGNAL(signal))

This function creates an Observable<QVariant> object which contains a future to represent the result of the signal. You could obtain the future by the future() method. And observe the result by subscribe() / context() methods. The result of the future is equal to the first parameter of the signal.

QFuture<QVariant> future = observe(timer, SIGNAL(timeout()).future();

See Observable<T>

Added since v0.4

Observable::onProgress(callback)

Listens the progressValueChanged and progressRangeChanged signal from the observed future then trigger the callback. The callback function may return nothing or a boolean value. If the boolean value is false, it will remove the listener such that the callback may not trigger anymore.

Example

QFuture<int> future = QtConcurrent::mapped(input, workerFunction);

AsyncFuture::observe(future).onProgress([=]() -> bool {
    qDebug() << future.progressValue();
    return true;
});

// or

AsyncFuture::observe(future).onProgress([=]() -> void {
    qDebug() << future.progressValue();
});

Deferred::track(future)

Track the progress and synchronize the status of the target future object.

It will forward the signal of started , resumed , paused . And synchonize the progressValue, progressMinimum and progressMaximum value by listening the progressValueChanged signal from target future object.

Remarks: It won't complete a future even the progressValue has been reached the maximum value.

Deferred<T>::complete(QList<T>)

Complete the future object with a list of result. A user may obtain all the value by QFuture::results().

API Changes

Deferred::complete(QFuture<T> target)

Complete the deferred according to the target future. It will also track the progressValue and status.

AsyncFuture::combine()

Supported to combine deferred object

Observable<void> o;
auto all = combine() << o;

v0.3.4

7 years ago

Experimental New API

  1. Deferred<T>::complete(QList<T>)

Bug Fixed

  1. A dangling pointer issue when using combine() [Only affect v0.3.3]
  2. subscribe() does not able to use in non-main thread.

Changes

  1. subscribe() - Run callback on main thread in any case.
  2. subscribe() - Able to handle a callback with return type of QFuture
  3. context() - Supported to use across thread

v0.3

7 years ago

API Changes

The input argument of combine() has been changed from bool to CombinatorMode. The default value is FailFast.

typedef enum {
    FailFast,
    AllSettled
} CombinatorMode;

Bug Fixes

  1. Create a context observable within a context function may hang.
  2. Memory leak when QFuture is returned from callback function

v0.2

7 years ago

Global

  1. Completely dropped the use of QVariant. AsyncFuture won’t require user to register their type in Qt’s meta type system
  2. Fixed few compilation problems in GCC 4.6

Observable::subscribe()

  • Now it return Observable<R> instead of void

Combinator

  • Changed the parent type from Observable<QVariantList> to Observable<void>. It won’t convert input results to QVariant anymore.

v0.1.1

7 years ago

Bug Fix

  1. Combinator crashes if multiple copy of instance was existed.

v0.1

7 years ago

Features

1. Convert a signal from QObject into a QFuture object


#include "asyncfuture.h"
using namespace AsyncFuture;

// Convert a signal from QObject into a QFuture object

QFuture<void> future = observe(timer,
                               &QTimer::timeout).future();

/* Listen from the future without using QFutureWatcher<T>*/
observe(future).subscribe([]() {
    // onCompleted. It is invoked when the observed future is finished successfully
    qDebug() << "onCompleted";
},[]() {
    // onCanceled
    qDebug() << "onCancel";
});

/* It is chainable. Listen from a timeout signal only once */
observe(timer, &QTimer::timeout).subscribe([=]() { /*…*/ });

2. Combine multiple futures with different type into a single future object

/* Combine multiple futures with different type into a single future */

QFuture<QImage> f1 = QtConcurrent::run(readImage, QString("image.jpg"));

QFuture<void> f2 = observe(timer, &QTimer::timeout).future();

(combine() << f1 << f2).subscribe([](QVariantList result){
    // Read an image
    // result[0] = QImage
    qDebug() << result;
});

3. Advanced multi-threading model

/* Start a thread and process its result in main thread */

QFuture<QImage> reading = QtConcurrent::run(readImage, QString("image.jpg"));

QFuture<bool> validating = observe(reading).context(contextObject, validator).future();

    // Read image by a thread, when it is ready, run the validator function
    // in the thread of the contextObject(e.g main thread)
    // And it return another QFuture to represent the final result.

/* Start a thread and process its result in main thread, then start another thread. */

QFuture<int> f1 = QtConcurrent::mapped(input, mapFunc);

QFuture<int> f2 = observe(f1).context(contextObject, [=](QFuture<int> future) {
    // You may use QFuture as the input argument of your callback function
    // It will be set to the observed future object. So that you may obtain
    // the value of results()

    qDebug() << future.results();

    // Return another QFuture is possible.
    return QtConcurrent::run(reducerFunc, future.results());
}).future();

// f2 is constructed before the QtConcurrent::run statement
// But its value is equal to the result of reducerFunc

4. Promise like interface

The deferred<T>() function return a Deferred<T> object that allows you to manipulate a QFuture manually. The future() function return a forever running QFuture<T> unless you have called Deferred.complete() / Deferred.cancel() manually, or the Deferred object is destroyed without observed any future.

The usage of complete/cancel with a Deferred object is pretty similar to the resolve/reject in a Promise object. You could complete a future by calling complete with a result value. If you give it another future, then it will observe the input future and change status once that is finished.

Complete / cancel a future on your own choice

// Complete / cancel a future on your own choice
auto d = deferred<bool>();

observe(d.future()).subscribe([]() {
    qDebug() << "onCompleted";
}, []() {
    qDebug() << "onCancel";
});

d.complete(true); // or d.cancel();

QCOMPARE(d.future().isFinished(), true);
QCOMPARE(d.future().isCanceled(), false);

Complete / cancel a future according to another future object.

// Complete / cancel a future according to another future object.

auto d = deferred<void>();

d.complete(QtConcurrent::run(timeout));

QCOMPARE(d.future().isFinished(), true);
QCOMPARE(d.future().isCanceled(), false);

Read a file. If timeout, cancel it.


auto timeout = observe(timer, &QTimer::timeout).future();

auto defer = deferred<QString>();

defer.complete(QtConcurrent::run(readFileworker, fileName));
defer.cancel(timeout);

return defer.future();

More examples are available at : asyncfuture/example.cpp at master · benlau/asyncfuture

Installation

AsyncFuture is a single header library. You could just download the asyncfuture.h in your source tree or install it via qpm

qpm install async.future.pri 

or

wget https://raw.githubusercontent.com/benlau/asyncfuture/master/asyncfuture.h