Asynqro
Futures and thread pool for C++ (with optional Qt support)
Install / Use
/learn @dkormalev/AsynqroREADME
Asynqro
Asynqro is a small library with purpose to make C++ programming easier by giving developers rich monadic Future API (mostly inspired by Future API in Scala language). This library is another implementation of ideas in https://github.com/opensoft/proofseed (now moved to asynqro usage, for historic purposes check tags before 02/25/19), but has much cleaner API, refined task scheduling logic and is not tied to any framework.
Dependencies
- C++17: Should work with Clang
>=6, GCC>=7and MSVC>=16. Tested with Clang9 (travis), GCC9 (travis) and MSVC16 (VS2019, appveyor). Also tested for compatibility with clang 6.0 and gcc 7.4 on Ubuntu Bionic image (travis). Conan packages available with help of bincrafters. - CMake
>= 3.12.0 - GoogleTest. Will be automatically downloaded during cmake phase.
- lcov
>= 1.14. Used for code coverage calculation, not needed for regular build. - Optional Qt5
>= 5.10. It is not required though and by default asynqro is built without Qt support. There is no Qt dependency in library itself, but enabling it brings support for Qt containers, addsFuture::fromQtSignal()andFuture::fromQtFuture(). AlsoFuture::wait()becomes guithread-aware.
Asynqro has two main parts:
Future/Promise
There are already a lot of implementations of Future mechanism in C++:
std::future- no API at all, except very basic operations like retrieve value and wait for it. There is a Concurrency TS with.then, but it is still not in the standard.boost::future- almost the same asstd::future.QFuture- Mostly unusable outside of QtConcurrent without Qt private headers because there is no way to fill it from user code.Folly- Folly futures are also inspired by Scala ones (but different ones, from Twitter framework) and have good API but are a part of huge framework which is too hard to use partially.- Many others not mentioned here
So why not to create another one?
Future-related part of asynqro contains:
All classes are reentrant and thread-safe.
All higher-order methods are exception-safe. If any exception happens inside function passed to such method, then Future will fail (or task will gracefully stop if it is runAndForget).
It is possible to use Future with movable-only classes (except sequence()). In this case resultRef() should be used instead of result().
Asynqro is intended to be used by including asynqro/asynqro header that includes asynqro/future.h and asynqro/tasks.h. It is also possible to include only asynqro/futures.h if task scheduling is not needed. simplefuture.h provides simple wrapper with std::any as failure type. All other headers except these three are considered as implementation and should not be included directly.
Good example of customizing Future to specific needs can be found in https://github.com/opensoft/proofseed/blob/develop/include/proofseed/asynqro_extra.h .
Promise
There is not a lot of methods in this class and its main usage is to generate Future object which later will be filled by this Promise at most one time. All subsequent fills will be ignored.
Future
This class shouldn't be instantiated directly in users code, but rather is obtained either from Promise or from tasks scheduling part. Also new Future object is returned from all transformation Future methods. It complies with functor and monad laws from FP and provides all operators required by them (successful/failed, map, flatMap).
Future is also sort of left-biased EitherT with result type as left side and failure type as right value (to provide failure reason). Sides were chosen non-canonical way (typical Either usually has right side as result and left as error) for compatibility purposes: std::expected type in C++ is sided the same way.
Almost all Future methods returns Future object, so they can be effectively chained. Futures are almost immutable. Almost because they will change there state at most one time when they are filled. Adding callbacks doesn't change behavior of other already applied callbacks (i.e. it will not change state of Future and/or its value).
if higher-order method is called on already filled Future it will be called (in case of matching status) immediately in the same thread. If it is not yet filled, it will be put to queue, which will be called (in non-specified order) on Future filling in thread that filled the Future.
Future API
successful-T->Future<T, FailureType>creates new Future object filled as successful with provided valuefailed-FailureType->Future<T, FailureType>creates new Future object filled as failed with provided reasonfromQtSignal-(QObject, Signal)->Future<T, FailureType>creates new Future object that will be filled when signal emits.There should be either bool or same type as signal parameter (if signal has more than one parameterTshould bestd::tuple)fromQtFuture-QFuture<OtherT>->Future<T, FailureType>creates new Future object that will be filled with QFuture result.OtherTandTmust follow next rules:- if
OtherTisvoidthenTmust bebool - if
TisboolandOtherTis not convertible toboolthen result will always betrue - if
Tis a container ofOtherTall results from QFuture will be used - if nothing above is true then
OtherTmust be convertible toTand in this case first result from QFuture will be used
- if
wait- waits for Future to be filled (either as successful or as failed) if it is not yet filled with optional timeoutisCompleted/isFailed/isSucceeded- returns current state of Futureresult/resultRef/failureReason- returns result of Future or failure reason. Will wait for Future to be filled if it isn't already.onSuccess-(T->void)->Future<T, FailureType>adds a callback for successful case.onFailure-(FailureType->void)->Future<T, FailureType>adds a callback for failure case.filter-(T->bool, FailureType)->Future<T, FailureType>fails Future if function returns false for filled value.map-(T->U)->Future<U, FailureType>transforms Future inner type. Also available as>>operator.mapFailure-(FailureType->OtherFailureType)->Future<T, OtherFailureType>transforms Future failure type.flatMap-(T->Future<U, FailureType>)->Future<U, FailureType>transforms Future inner type. Also available as>>operator.andThen-(void->Future<U, FailureType>)->Future<U, FailureType>shortcut for flatMap if value of previous Future doesn't matter. Also available as>>operator.andThenValue-U->Future<U, FailureType>shortcut for andThen if all we need is to replace value of successful Future with some already known value.innerReduce/innerMap/innerFilter/innerFlatten- applicable only for Future with sequence inner type. Allows to modify sequence by reducing, mapping, filtering or flattening it.recover-(FailureType->T)->Future<T, FailureType>transform failed Future to successfulrecoverWith-(FailureType->Future<T, FailureType>)->Future<T, FailureType>the same as recover, but allows to return Future in callbackrecoverValue-T->Future<T, FailureType>shortcut for recover when we just need to replace with some already known valuezip-(Future<U, FailureType>, ...) -> Future<std::tuple<T, U, ...>, FailureType>combines values from different Futures. If any of the Futures already have tuple as inner type, then U will be list of types from this std::tuple (so resulting tuple will be a flattened one). If zipped Futures have different FailureTypes then they will be combined in std::variant (with flattening if some of FailureTypes are already variants). Also available as+operator.zipValue-U->Future<std::tuple<T, U>, FailureType>- shortcut for zip with already known value.sequence-Sequence<Future<T, FailureType>> -> Future<Sequence<T>, FailureType>transformation from sequence of Futures to single Future.sequenceWithFailures-Sequence<Future<T, FailureType>> -> Future<std::pair<AssocSequence<Sequence::size_type, T>, AssocSequence<Sequence::size_type, FailureType>>, FailureType>transformation from sequence of Futures to single Future with separate containers for successful Futures and failed ones.AssocSequencecan be set as optional type parameter.
CancelableFuture
API of
