pax_global_header00006660000000000000000000000064137612215350014517gustar00rootroot0000000000000052 comment=0da13489746f0412e6bad66bfe0acef54915adb2 asyncfuture/000077500000000000000000000000001376122153500134335ustar00rootroot00000000000000asyncfuture/.gitignore000066400000000000000000000005151376122153500154240ustar00rootroot00000000000000# C++ objects and libs *.slo *.lo *.o *.a *.la *.lai *.so *.dll *.dylib # Qt-es /.qmake.cache /.qmake.stash *.pro.user *.pro.user.* *.qbs.user *.qbs.user.* *.moc moc_*.cpp qrc_*.cpp ui_*.h Makefile* *build-* # QtCreator *.autosave # QtCtreator Qml *.qmlproject.user *.qmlproject.user.* # QtCtreator CMake CMakeLists.txt.user asyncfuture/.travis.yml000066400000000000000000000024121376122153500155430ustar00rootroot00000000000000language : cpp dist: trusty env: - QT_VERSION="5.7.1" TARGET_PACKAGE="qt.57.gcc_64" - QT_VERSION="5.10.1" TARGET_PACKAGE="qt.qt5.5101.gcc_64" addons: apt: packages: - valgrind - jq compiler: - gcc before_install: - export GOPATH=`pwd`/gosrc - export PATH=`pwd`/gosrc/bin:$PATH - go get qpm.io/qpm - sh -e /etc/init.d/xvfb start script: - echo $TARGET_NAME - qpm check - VERSION=$(jq .version.label qpm.json) - VERSION=$(eval echo $VERSION) - ulimit -c unlimited - ulimit -a - if ! head -n 1 asyncfuture.h | grep -q $VERSION; then echo "Version string mismatched"; exit -1 ;fi - git clone https://github.com/benlau/qtci.git - export QT_CI_PACKAGES=${TARGET_PACKAGE} - source qtci/path.env - install-qt ${QT_VERSION} `pwd`/Qt - ls - ls Qt - source Qt/qt-${QT_VERSION}.env - mkdir build/ - cd build - run-unittests ../tests/asyncfutureunittests/asyncfutureunittests.pro - ls - if test -f coredump; then gdb -ex "where \n ; thread apply all bt" asyncfutureunittests coredump ; fi - valgrind --num-callers=30 --leak-check=full --track-origins=yes --gen-suppressions=all --error-exitcode=1 --suppressions=../tests/asyncfutureunittests/asyncfuture.supp ./asyncfutureunittests asyncfuture/LICENSE000066400000000000000000000261351376122153500144470ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. asyncfuture/README.md000066400000000000000000000616141376122153500147220ustar00rootroot00000000000000## AsyncFuture - Use QFuture like a Promise object [![Build Status](https://travis-ci.org/benlau/asyncfuture.svg?branch=master)](https://travis-ci.org/benlau/asyncfuture) [![Build status](https://ci.appveyor.com/api/projects/status/5cndw1uu5ay960c4?svg=true)](https://ci.appveyor.com/project/benlau/asyncfuture) QFuture is used together with QtConcurrent to represent the result of an asynchronous computation. It is a powerful component for multi-thread programming. But its usage is limited to the result of threads, it doesn't work with the asynchronous signal emitted by QObject. And it is a bit trouble to setup the listener function via QFutureWatcher. AsyncFuture is designed to enhance the function to offer a better way to use it for asynchronous programming. It provides a Promise object like interface. This project is inspired by AsynQt and RxCpp. Remarks: You may use this project together with [QuickFuture](https://github.com/benlau/quickfuture) for QML programming. Reference Articles ------------------ 1. [Multithreading Programming with Future & Promise – E-Fever – Medium](https://medium.com/e-fever/multithreading-programming-with-future-promise-2d35e13b9404) 1. [AsyncFuture Cookbook 1 — Calling QtConcurrent::mapped within the run function](https://medium.com/e-fever/asyncfuture-cookbook-1-calling-qtconcurrent-mapped-within-the-run-function-ba58d523a0ce) Features ======== 1. Convert a signal from QObject into a QFuture object 2. Combine multiple futures with different type into a single future object 3. Use QFuture like a Promise object 4. Chainable Callback - Advanced multi-threading programming model **1. Convert a signal from QObject into a QFuture object** ```c++ #include "asyncfuture.h" using namespace AsyncFuture; // Convert a signal from QObject into a QFuture object QFuture future = observe(timer, &QTimer::timeout).future(); /* Listen from the future without using QFutureWatcher*/ observe(future).subscribe([]() { // onCompleted. It is invoked when the observed future is finished successfully qDebug() << "onCompleted"; },[]() { // onCanceled qDebug() << "onCancel"; }); ``` **2. Combine multiple futures with different type into a single future object** ```c++ /* Combine multiple futures with different type into a single future */ QFuture f1 = QtConcurrent::run(readImage, QString("image.jpg")); QFuture f2 = observe(timer, &QTimer::timeout).future(); QFuture result = (combine() << f1 << f2).subscribe([=](){ // Read an image but do not return before timeout return f1.result(); }).future(); QCOMPARE(result.progressMaximum(), 2); // Added since v0.4.1 ``` **3. Use QFuture like a Promise object** Create a QFuture then complete / cancel it by yourself. ```c++ // Complete / cancel a future on your own choice auto d = deferred(); d.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. ```c++ // Complete / cancel a future according to another future object. auto d = deferred(); d.complete(QtConcurrent::run(timeout)); QCOMPARE(d.future().isFinished(), false); QCOMPARE(d.future().isCanceled(), false); ``` Read a file. If timeout, cancel it. ```c++ auto timeout = observe(timer, &QTimer::timeout).future(); auto defer = deferred(); defer.complete(QtConcurrent::run(readFileworker, fileName)); defer.cancel(timeout); return defer.future(); ``` **4. Chainable Callback - Advanced multi-threading programming model** Futures can be chained into a sequence of process. And represented by a single future object. ```c++ /* Start a thread and process its result in main thread */ QFuture readImage(const QString& file) { auto readImageWorker = [=]() { QImage image; image.read(file); return image; }; auto updateCache = [&](QImage image) { m_cache[file] = image; return image; }; QFuture reading = QtConcurrent::run(readImageWorker)); return observe(reading).subscribe(updateCache).future(); } // Read image by a thread, when it is ready, run the updateCache function // in the main thread. // And it return another QFuture to represent the final result. ``` ```c++ /* Start a thread and process its result in main thread, then start another thread. */ QFuture f1 = QtConcurrent::mapped(input, mapFunc); QFuture f2 = observe(f1).subscribe([=](QFuture 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 ``` More examples are available at : [asyncfuture/example.cpp at master · benlau/asyncfuture](https://github.com/benlau/asyncfuture/blob/master/tests/asyncfutureunittests/example.cpp) 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 API === AsyncFuture::observe(QObject* object, PointerToMemberFunc signal) ------------------- This function creates an Observable<ARG> 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 ARG type is equal to the first parameter of the signal. If the signal does not contain any argument, ARG will be void. In case it has more than one argument, the rest will be ignored. ```c++ QFuture f1 = observe(timer, &QTimer::timeout).future(); QFuture f2 = observe(button, &QAbstractButton::toggled).future(); ``` See [Observable``](#observablet) AsyncFuture::observe(object, SIGNAL(signal)) ---------------------- This function creates an `Observable` 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. ```c++ QFuture future = observe(timer, SIGNAL(timeout()).future(); ``` See [Observable``](#observablet) AsyncFuture::observe(QFuture<T> future) ------------- This function creates an Observable<T> object which provides an interface for observing the input future. See [Observable``](#observablet) ```c++ // Convert a signal from QObject into QFuture QFuture future = observe(button, &QAbstractButton::toggled).future(); // Listen from the future without using QFutureWatcher observe(future).subscribe([](bool toggled) { // onCompleted. It is invoked when the observed future is finished successfully qDebug() << "onCompleted"; },[]() { // onCanceled qDebug() << "onCancel"; }); ``` AsyncFuture::observe(QFuture<QFuture<T>T> future) ----- This function creates an Observable object which provides an interface for observing the input future. That is designed to handle following use-case: ``` QFuture 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> auto defer = AsyncFuture::deferred(); // 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``](#observablet) AsyncFuture::combine(CombinatorMode mode = FailFast) ------------ This function creates a Combinator object (inherit `Observable`) for combining multiple future objects with different type into a single future. For example: ```c++ QFuture f1 = QtConcurrent::run(readImage, QString("image.jpg")); QFuture f2 = observe(timer, &QTimer::timeout).future(); auto combinator = combine(AllSettled) << f1 << f2; QFuture result = combinator.subscribe([=](){ // Read an image but do not return before timeout return f1.result(); }).future(); QCOMPARE(combinator.progressMaximum, 2); ``` Once all the observed futures finished, the contained future will be finished too. And it will be cancelled immediately if any one of the observed future is cancelled in fail fast mode. In case you want the cancellation take place after all the futures finished, you should set mode to `AsyncFuture::AllSettled`. Since v0.4.1, the `progressValue` and `progressMaximum` of the obtained future will be set. Since v0.3.6, you may assign a deferred object to Combinator directly. Example ``` QFuture f1 = QtConcurrent::run(readImage, QString("image.jpg")); auto defer = deferred(); QFuture result = (combine(AllSettled) << f1 << defer).subscribe([=](){ // Read an image but do not return before the deferred is completed return f1.result(); }).future(); ``` AsyncFuture::deferred<T>() ---------- The deferred() function return a Deferred object that allows you to set QFuture completed/cancelled manually. ```c++ auto d = deferred(); d.subscribe([]() { qDebug() << "onCompleted"; }, []() { qDebug() << "onCancel"; }); d.complete(true); // or d.cancel(); ``` See [`Deferred`](#deferredt) ![AsyncFuture Class Diagram](https://raw.githubusercontent.com/benlau/junkcode/master/docs/AsyncFuture%20Class%20Diagram.png) Observable<T> ------------ Observable<T> is a chainable utility class for observing a QFuture object. It is created by the observe() function. It can register multiple callbacks to be triggered in different situations. And that will create a new Observable<T> / QFuture object to represent the result of the callback function. It may even call QtConcurrent::run() within the callback function to run the funciton in another thread. Therefore, it could create a more complex/flexible workflow. ``` QFuture future Observable observable1 = AsyncFuture::observe(future); // or auto observable2 = AsyncFuture::observe(future); ``` **QFuture<T> Observable<T>::future()** Obtain the QFuture object to represent the result. **Observable<T> Observable<T>::subscribe(Completed onCompleted, Canceled onCanceled)** Observable Observable::subscribe(Completed onCompleted); Observable Observable::subscribe(Completed onCompleted, Canceled onCanceled); Register a onCompleted and/or onCanceled callback to the observed QFuture object. Unlike the context() function, the callbacks will be triggered on the main thread. The return value is an `Observable` object where R is the return type of the onCompleted callback. Remarks: Before v0.3.2, the callback will be executed in the current thread. See [Subscribe Callback Function](#subscribe-callback-funcion) Example: ```c++ QFuture future = observe(button, &QAbstractButton::toggled).future(); // Listen from the future without using QFutureWatcher observe(future).subscribe([](bool toggled) { // onCompleted. It is invoked when the observed future is finished successfully qDebug() << "onCompleted"; },[]() { // onCanceled qDebug() << "onCancel"; }); ``` **Observable<R> Observable<T>::context(QObject* contextObject, Completed onCompleted, Cancel onCanceled)** *This API is for advanced users only* Add a callback function that listens to the finished and canceled signals from the observing QFuture object. The callback is invoked in the thread of the context object. In case the context object is destroyed before the finished signal, the callback functions (onCompleted and onCanceled) won't be triggered and the returned Observable object will cancel its future. Note: An event loop, must be excuting on the the contextObject->thread() for nested observe().context() calls to work. Threads on the QThreadPool, generally don't have a QEventLoop executing, so manually creating and calling QEventLoop is necessary. For example: ```c++ auto worker = [&]() { auto localTimeout = [](int sleepTime) { return QtConcurrent::run([sleepTime]() { QThread::currentThread()->msleep(sleepTime); }); }; QEventLoop loop; auto context = QSharedPointer::create(); QThread* workerThread = QThread::currentThread(); observe(localTimeout(50)).context(context.get(), [localTimeout, context]() { qDebug() << "First time localTimeout() finished return localTimeout(50); }).context(context.get(), [context, &called, workerThread, &loop]() { qDebug() << "Second time localTimeout() finished loop.quit(); }); loop.exec(); }; QtConcurrent::run(worker); ``` The return value is an `Observable` object where R is the return type of the onCompleted callback. ```c++ auto validator = [](QImage input) -> bool { /* A dummy function. Return true for any case. */ return true; }; QFuture reading = QtConcurrent::run(readImage, QString("image.jpg")); QFuture validating = observe(reading).context(contextObject, validator).future(); ``` In the above example, the result of `validating` is supposed to be true. However, if the `contextObject` is destroyed before `reading` future finished, it will be cancelled and the result will become undefined. **void Observable<T>::onProgress(Functor 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 will not be triggered anymore. Example ```c++ QFuture future = QtConcurrent::mapped(input, workerFunction); AsyncFuture::observe(future).onProgress([=]() -> bool { qDebug() << future.progressValue(); return true; }); // or AsyncFuture::observe(future).onProgress([=]() -> void { qDebug() << future.progressValue(); }); ``` Added since v0.3.6.4 **Chained Progress** `observe().subscribe().future()` future will report progress accordingly to the underlying future chain. When watching the final future in the chain, `progressRangeChanged` may be be updated multiple times as futures in the chain update their individual `progressRangeChanged`. When visualizing final future's progress in a progress bar, progressValue may appear to go in reverse, as progressRange increases. `progressValueChanged` will never go down as execution continues. Example: ```{c++} QVector ints(100); std::iota(ints.begin(), ints.end(), ints.size()); // Make ints from 1 to 100, increament by 1 // Worker function std::function func = [](int x)->int { QThread::msleep(100); return x * x; }; //First execution of workers //Will increase the progressRange to 100 QFuture mappedFuture = QtConcurrent::mapped(ints, func); auto nextFuture = AsyncFuture::observe(mappedFuture).subscribe([ints, func](){ //Execute another batch of workers //Will increase the progressRange to 200 QFuture mappedFuture2 = QtConcurrent::mapped(ints, func); return mappedFuture2; }).future(); AsyncFuture::observe(nextFuture).onProgress([nextFuture](){ //Report the progress for the sum of mappedFuture and nextFuture from 0 to 200. }); ``` Deferred<T> ----------- The `deferred()` function return a Deferred object that allows you to manipulate a QFuture manually. The future() function return a running QFuture. You have to call Deferred.complete() / Deferred.cancel() to trigger the status changes. The usage of complete/cancel in 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. `deffered()` that are created and immediately completed it's recommend to use `completed()` instead. **Auto Cancellation** The `Deferred` object is an explicitly shared class. You may own multiple copies and they are pointed to the same piece of shared data. In case, all of the instances are destroyed, it will cancel its future automatically. But there has an exception if you have even called Deferred.complete(`QFuture`) / Deferred.cancel(`QFuture`) then it won't cancel its future due to destruction. That will leave to the observed future to determine the final state. ```c++ QFuture future; { auto defer = deferred(); future = defer.future(); } QCOMPARE(future.isCanceled(), true); // Due to auto cancellation ``` ```c++ QFuture future; { auto defer = deferred(); future = defer.future(); defer.complete(QtConcurrent::run(worker)); } QCOMPARE(future.isCanceled(), false); ``` **Deferred<T>::complete(T) / Deferred<T>::complete()** Complete this future object with the given arguments **Deferred<T>::complete(QList<T>)** Complete the future object with a list of result. User may obtain all the value by QFuture::results(). **Deferred<T>::complete(QFuture<T>)** This future object is deferred to complete/cancel. It will track the state from the input future. If the input future is completed, then it will be completed too. That is same for cancel. **Deferred<T>::cancel()** Cancel the future object **Deferred<T>::cancel(QFuture)** This future object is deferred to cancel according to the input future. Once it is completed, this future will be cancelled. However, if the input future is cancelled. Then this future object will just ignore it. Unless it fulfils the auto-cancellation rule. **Deferred<T>::track(QFuture target)** 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. Added since v0.3.6 completed(); ----------- The `completed(const T&)` and `completed()` function return finished `QFuture` and `QFuture` . `completed(const T&)` can be used instead of a `deferred()` when the result is already available. For example: ```c++ auto defer = deferred(); defer.complete(5); auto completedFuture = defer.future(); ``` is equivalent to ```c++ auto completedFuture = completed(5); ``` `completed(const T&)` is more convenient and light weight (memory and performance efficient) method of creating a completed `QFuture`. Example ``` auto defer = AsyncFuture::deferred(); auto mappedFuture = QtConcurrent::mapped(data, workerFunc); defer.track(mappedFuture); AsyncFuture::observe(mappedFuture).subscribute([=]() mutable { defer.complete(); }); return defer.future(); // It is a future with progress value same as the mappedFuture, but it don't contains the result. ``` Advanced Topics ======= Subscribe Callback Funcion --------------- In subscribe() / context(), you may pass a function with zero or one argument as the onCompleted callback. If you give it an argument, the type must be either of T or QFuture. That would obtain the result or the observed future itself. ```c++ QFuture reading = QtConcurrent::run(readImage, QString("image.jpg")); observe(reading).subscribe([]() { }); observe(reading).subscribe([](QImage result) { }); observe(reading).subscribe([](QFuture future) { // In case you need to get future.results }); ``` The return type can be none or any kind of value. That would determine what type of `Observable` generated by context()/subscribe(). In case, you return a QFuture object. Then the new `Observable` object will be deferred to complete/cancel until your future object is resolved. Therefore, you could run QtConcurrent::run within your callback function to make a more complex/flexible multi-threading programming models. ```c++ QFuture f1 = QtConcurrent::mapped(input, mapFunc); QFuture f2 = observe(f1).context(contextObject, [=](QFuture 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 thread 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 ``` Callback Chain Cancelation ---- A chain can be canceled by returning a canceled QFuture. Example: ```c++ auto f2 = observe(f1).subscribe([=]() { auto defer = Deferred(); defer.cancel(); return defer.future(); }).future(); observe(f2).subscribe([=]() { // it won't be executed. }); ``` Cancelling the future at the end of the chain will cancel the whole chain. This will cancel all `QtConcurrent` execution. Worker threads that have already been started by `QtConcurrent` will continue running until finished, but no new ones will be started (this is how `QtConcurrent` works). Example: ```c++ QVector ints(100); std::iota(ints.begin(), ints.end(), ints.size()); std::function func = [](int x)->int { QThread::msleep(100); return x * x; }; QFuture mappedFuture = QtConcurrent::mapped(ints, func); auto future = AsyncFuture::observe(mappedFuture).subscribe( []{ // it won't be executed }, []{ // it will be executed. } ).future(); future.cancel(); //Will cancel mappedFuture and future ``` Future Object Tracking --------------- Since v0.4, the deferred object is 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: ```c++ auto defer = AsyncFuture::deferred(); QFuture input = QtConcurrent::mapped(files, readImage); defer.complete(input); // defer.future() will be a mirror of `input`. The `progressValue` will be changed and it will emit "started" signal via QFutureWatcher ``` A practical use-case ```c++ QFuture 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> auto defer = AsyncFuture::deferred(); // 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 Examples ======== There has few examples of different use-cases in this source file: [asyncfuture/example.cpp at master · benlau/asyncfuture](https://github.com/benlau/asyncfuture/blob/master/tests/asyncfutureunittests/example.cpp) Building Testcases ================== qpm needs to install, see download instructions at http://www.qpm.io/ After cloning the asyncfuture repository run the following commands: ```shell cd asyncfuture/tests/asyncfutureunittests cat qpm.json ``` qpm.json should look something like this: ```json { "dependencies": [ "com.github.benlau.testable@1.0.2.22", "com.github.benlau.qtshell@0.4.6", "net.efever.xbacktrace@0.0.1" ] } ``` Install all the dependencies like this: ```shell qpm install com.github.benlau.testable@1.0.2.22 qpm install com.github.benlau.qtshell@0.4.6 qpm install net.efever.xbacktrace@0.0.1 ``` Now open asyncfuture.pro in QtCreator and build and run testcases. asyncfuture/appveyor.yml000066400000000000000000000013201376122153500160170ustar00rootroot00000000000000version: build{build} branches: except: - project/travis environment: matrix: - name: win32 platform: amd64_x86 qt: msvc2017 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 build_script: - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" - set GOPATH=c:\gopath - set QTDIR=C:\Qt\5.9.7\msvc2017_64 - set PATH=%PATH%;%QTDIR%\bin;C:\MinGW\bin;%GOPATH%\bin; - go get qpm.io/qpm - go install qpm.io/qpm - dir %GOPATH%\bin - cd tests/asyncfutureunittests - qpm install - qmake asyncfutureunittests.pro - nmake - dir /w - dir release /w - release\asyncfutureunittests - cd ..\compilererrors - qmake - nmake & exit 0 asyncfuture/asyncfuture.h000066400000000000000000001406301376122153500161600ustar00rootroot00000000000000/* AsyncFuture Version: 0.4.1 */ #pragma once #include #include #include #include #include #include #include #include #define ASYNCFUTURE_ERROR_OBSERVE_VOID_WITH_ARGUMENT "Observe a QFuture but your callback contains an input argument" #define ASYNCFUTURE_ERROR_CALLBACK_NO_MORE_ONE_ARGUMENT "Callback function should not take more than 1 argument" #define ASYNCFUTURE_ERROR_ARGUMENT_MISMATCHED "The callback function is not callable. The input argument doesn't match with the observing QFuture type" #define ASYNC_FUTURE_CALLBACK_STATIC_ASSERT(Tag, Completed) \ static_assert(Private::arg_count::value <= 1, Tag ASYNCFUTURE_ERROR_CALLBACK_NO_MORE_ONE_ARGUMENT); \ static_assert(!(std::is_same::value && Private::arg_count::value >= 1), Tag ASYNCFUTURE_ERROR_OBSERVE_VOID_WITH_ARGUMENT); \ static_assert( Private::decay_is_same>::value || \ Private::arg0_is_future::value || \ std::is_same>::value, Tag ASYNCFUTURE_ERROR_ARGUMENT_MISMATCHED); namespace AsyncFuture { /* Naming Convention * * typename T - The type of observable QFuture * typename R - The return type of callback */ namespace Private { /* Begin traits functions */ // Determine is the input type a QFuture template struct future_traits { enum { is_future = 0 }; typedef void arg_type; }; template