diff --git a/src/async.h b/src/async.h index fcb7885..71bd621 100644 --- a/src/async.h +++ b/src/async.h @@ -1,732 +1,732 @@ /* * Copyright 2014 - 2015 Daniel Vrátil * Copyright 2016 Daniel Vrátil * Copyright 2016 Christian Mollekopf * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library. If not, see . */ #ifndef KASYNC_H #define KASYNC_H #include "kasync_export.h" #include #include #include #include #include #include "future.h" #include "debug.h" #include "async_impl.h" #include #include #include #include /** * @mainpage KAsync * * @brief API to help write async code. * * This API is based around jobs that take lambdas to execute asynchronous tasks. * Each async operation can take a continuation that can then be used to execute * further async operations. That way it is possible to build async chains of * operations that can be stored and executed later on. Jobs can be composed, * similarly to functions. * * Relations between the components: * * Job: API wrapper around Executors chain. Can be destroyed while still running, * because the actual execution happens in the background * * Executor: Describes task to execute. Executors form a linked list matching the * order in which they will be executed. The Executor chain is destroyed when * the parent Job is destroyed. However if the Job is still running it is * guaranteed that the Executor chain will not be destroyed until the execution * is finished. * * Execution: The running execution of the task stored in Executor. Each call to * Job::exec() instantiates new Execution chain, which makes it possible for * the Job to be executed multiple times (even in parallel). * * Future: Representation of the result that is being calculated * * * TODO: Possibility to abort a job through future (perhaps optional?) * TODO: Support for timeout, specified during exec call, after which the error * handler gets called with a defined errorCode. */ namespace KAsync { template class Executor; class JobBase; template class Job; template using HandleContinuation = typename detail::identity&)>>::type; template using HandleErrorContinuation = typename detail::identity&)>>::type; template using SyncContinuation = typename detail::identity>::type; template using SyncErrorContinuation = typename detail::identity>::type; template using JobContinuation = typename detail::identity(In ...)>>::type; template using JobErrorContinuation = typename detail::identity(const KAsync::Error &, In ...)>>::type; //@cond PRIVATE namespace Private { class ExecutorBase; typedef QSharedPointer ExecutorBasePtr; struct KASYNC_EXPORT Execution { explicit Execution(const ExecutorBasePtr &executor); virtual ~Execution(); void setFinished(); template KAsync::Future* result() const { return static_cast*>(resultBase); } void releaseFuture(); ExecutorBasePtr executor; FutureBase *resultBase; ExecutionPtr prevExecution; Tracer *tracer; }; template struct ContinuationHelper { ContinuationHelper(const HandleContinuation &func) : handleContinuation(func) {}; ContinuationHelper(const HandleErrorContinuation &func) : handleErrorContinuation(func) {}; ContinuationHelper(const JobContinuation &func) : jobContinuation(func) {}; ContinuationHelper(const JobErrorContinuation &func) : jobErrorContinuation(func) {}; HandleContinuation handleContinuation; HandleErrorContinuation handleErrorContinuation; JobContinuation jobContinuation; JobErrorContinuation jobErrorContinuation; }; typedef QSharedPointer ExecutionPtr; class KASYNC_EXPORT ExecutorBase { template friend class Executor; template friend class KAsync::Job; friend struct Execution; friend class KAsync::Tracer; public: virtual ~ExecutorBase(); virtual ExecutionPtr exec(const ExecutorBasePtr &self) = 0; protected: ExecutorBase(const ExecutorBasePtr &parent); template KAsync::Future* createFuture(const ExecutionPtr &execution) const; ExecutorBasePtr mPrev; void prepend(const ExecutorBasePtr &e) { if (mPrev) { mPrev->prepend(e); } else { mPrev = e; } } void addToContext(const QVariant &entry) { mContext << entry; } QString mExecutorName; QVector mContext; }; enum ExecutionFlag { Always, ErrorCase, GoodCase }; template class Executor : public ExecutorBase { protected: Executor(const Private::ExecutorBasePtr &parent, ExecutionFlag executionFlag) : ExecutorBase(parent) , executionFlag(executionFlag) {} virtual ~Executor() {} virtual void run(const ExecutionPtr &execution) = 0; - ExecutionPtr exec(const ExecutorBasePtr &self); + ExecutionPtr exec(const ExecutorBasePtr &self) override; const ExecutionFlag executionFlag; private: void runExecution(const KAsync::Future &prevFuture, const ExecutionPtr &execution); }; } // namespace Private //@endcond template Job startImpl(const Private::ContinuationHelper &); template Job syncStartImpl(const SyncContinuation &); /** * @relates Job * * Start an asynchronous job sequence. * * start() is your starting point to build a chain of jobs to be executed * asynchronously. * * @param func A continuation to be executed. */ ///Sync void continuation without job: [] () -> T { ... } template KASYNC_EXPORT auto start(F func) -> typename std::enable_if::value, Job >::type { static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed."); return syncStartImpl(func); } ///Sync continuation without job: [] () -> T { ... } template KASYNC_EXPORT auto start(F func) -> typename std::enable_if()))>::value, Job())), In...> >::type { static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed."); return syncStartImpl(func); } ///Void continuation with job: [] () -> KAsync::Job<...> { ... } template KASYNC_EXPORT auto start(F func) -> typename std::enable_if::value, Job >::type { static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed."); return startImpl({func}); } ///continuation with job: [] () -> KAsync::Job<...> { ... } template KASYNC_EXPORT auto start(F func) -> typename std::enable_if()))>::value, Job()))::OutType, In...> >::type { static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed."); return startImpl({func}); } ///Handle continuation: [] (KAsync::Future, ...) { ... } template KASYNC_EXPORT auto start(const HandleContinuation &func) -> Job { static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed."); return startImpl({func}); } enum ControlFlowFlag { Break, Continue }; /** * @relates Job * * Async while loop. * * Loop continues while body returns ControlFlowFlag::Continue. */ KASYNC_EXPORT Job doWhile(Job body); /** * @relates Job * * Async while loop. * * Shorthand that takes a continuation. * * @see doWhile */ KASYNC_EXPORT Job doWhile(JobContinuation body); /** * @relates Job * * Async delay. */ KASYNC_EXPORT Job wait(int delay); /** * @relates Job * * A null job. * * An async noop. * */ template KASYNC_EXPORT Job null(); /** * @relates Job * * Async value. */ template KASYNC_EXPORT Job value(Out); /** * @relates Job * * Async foreach loop. * * This will execute a job for every value in the list. * Errors while not stop processing of other jobs but set an error on the wrapper job. */ template KASYNC_EXPORT Job forEach(KAsync::Job job); /** * @relates Job * * Async foreach loop. * * Shorthand that takes a continuation. * * @see serialForEach */ template KASYNC_EXPORT Job forEach(JobContinuation); /** * @relates Job * * Serial Async foreach loop. * * This will execute a job for every value in the list sequentially. * Errors while not stop processing of other jobs but set an error on the wrapper job. */ template KASYNC_EXPORT Job serialForEach(KAsync::Job job); /** * @relates Job * * Serial Async foreach loop. * * Shorthand that takes a continuation. * * @see serialForEach */ template KASYNC_EXPORT Job serialForEach(JobContinuation); /** * @relates Job * * An error job. * * An async error. * */ template KASYNC_EXPORT Job error(int errorCode = 1, const QString &errorMessage = QString()); /** * @relates Job * * An error job. * * An async error. * */ template KASYNC_EXPORT Job error(const char *); /** * @relates Job * * An error job. * * An async error. * */ template KASYNC_EXPORT Job error(const Error &); //@cond PRIVATE class KASYNC_EXPORT JobBase { template friend class Job; public: explicit JobBase(const Private::ExecutorBasePtr &executor); virtual ~JobBase(); protected: Private::ExecutorBasePtr mExecutor; }; //@endcond /** * @brief An Asynchronous job * * A single instance of Job represents a single method that will be executed * asynchronously. The Job is started by exec(), which returns Future * immediatelly. The Future will be set to finished state once the asynchronous * task has finished. You can use Future::waitForFinished() to wait for * for the Future in blocking manner. * * It is possible to chain multiple Jobs one after another in different fashion * (sequential, parallel, etc.). Calling exec() will then return a pending * Future, and will execute the entire chain of jobs. * * @code * auto job = Job::start>( * [](KAsync::Future> &future) { * MyREST::PendingUsers *pu = MyREST::requestListOfUsers(); * QObject::connect(pu, &PendingOperation::finished, * [&](PendingOperation *pu) { * future->setValue(dynamic_cast(pu)->userIds()); * future->setFinished(); * }); * }) * .each, int>( * [](const int &userId, KAsync::Future> &future) { * MyREST::PendingUser *pu = MyREST::requestUserDetails(userId); * QObject::connect(pu, &PendingOperation::finished, * [&](PendingOperation *pu) { * future->setValue(Qlist() << dynamic_cast(pu)->user()); * future->setFinished(); * }); * }); * * KAsync::Future> usersFuture = job.exec(); * usersFuture.waitForFinished(); * QList users = usersFuture.value(); * @endcode * * In the example above, calling @p job.exec() will first invoke the first job, * which will retrieve a list of IDs and then will invoke the second function * for each single entry in the list returned by the first function. */ template class Job : public JobBase { //@cond PRIVATE template friend class Job; template friend Job startImpl(const Private::ContinuationHelper &); template friend Job syncStartImpl(const SyncContinuation &); template friend Job forEach(KAsync::Job job); template friend Job serialForEach(KAsync::Job job); //@endcond public: typedef Out OutType; ///A continuation template Job then(const Job &job) const; ///Shorthands for a job that returns another job from it's continuation // //OutOther and InOther are only there fore backwards compatibility, but are otherwise ignored. //It should never be neccessary to specify any template arguments, as they are automatically deduced from the provided argument. // //We currently have to write a then overload for: //* One argument in the continuation //* No argument in the continuation //* One argument + error in the continuation //* No argument + error in the continuation //This is due to how we extract the return type with "decltype(func(std::declval()))". //Ideally we could conflate this into at least fewer overloads, but I didn't manage so far and this at least works as expected. ///Continuation returning job: [] (Arg) -> KAsync::Job<...> { ... } template auto then(F func) const -> typename std::enable_if()))>::value, Job()))::OutType, In...> >::type { using ResultJob = decltype(func(std::declval())); //Job return thenImpl({func}, Private::ExecutionFlag::GoodCase); } ///Void continuation with job: [] () -> KAsync::Job<...> { ... } template auto then(F func) const -> typename std::enable_if::value, Job >::type { using ResultJob = decltype(func()); //Job return thenImpl({func}, Private::ExecutionFlag::GoodCase); } ///Error continuation returning job: [] (KAsync::Error, Arg) -> KAsync::Job<...> { ... } template auto then(F func) const -> typename std::enable_if()))>::value, Job()))::OutType, In...> >::type { using ResultJob = decltype(func(KAsync::Error{}, std::declval())); //Job return thenImpl({func}, Private::ExecutionFlag::Always); } ///Error void continuation returning job: [] (KAsync::Error) -> KAsync::Job<...> { ... } template auto then(F func) const -> typename std::enable_if::value, Job >::type { using ResultJob = decltype(func(KAsync::Error{})); return thenImpl({func}, Private::ExecutionFlag::Always); } ///Sync continuation: [] (Arg) -> void { ... } template auto then(F func) const -> typename std::enable_if()))>::value, Job())), In...> >::type { using ResultType = decltype(func(std::declval())); //QString return syncThenImpl({func}, Private::ExecutionFlag::GoodCase); } ///Sync void continuation: [] () -> void { ... } template auto then(F func) const -> typename std::enable_if::value, Job >::type { using ResultType = decltype(func()); //QString return syncThenImpl({func}, Private::ExecutionFlag::GoodCase); } ///Sync error continuation: [] (KAsync::Error, Arg) -> void { ... } template auto then(F func) const -> typename std::enable_if()))>::value, Job())),In...> >::type { using ResultType = decltype(func(KAsync::Error{}, std::declval())); //QString return syncThenImpl({func}, Private::ExecutionFlag::Always); } ///Sync void error continuation: [] (KAsync::Error) -> void { ... } template auto then(F func) const -> typename std::enable_if::value, Job >::type { using ResultType = decltype(func(KAsync::Error{})); return syncThenImpl({func}, Private::ExecutionFlag::Always); } ///Shorthand for a job that receives the error and a handle template Job then(HandleContinuation func) const { return thenImpl({func}, Private::ExecutionFlag::GoodCase); } ///Shorthand for a job that receives the error and a handle template Job then(HandleErrorContinuation func) const { return thenImpl({func}, Private::ExecutionFlag::Always); } ///Shorthand for a job that receives the error only Job onError(const SyncErrorContinuation &errorFunc) const; /** * Shorthand for a forEach loop that automatically uses the return type of * this job to deduce the type exepected. */ template::value, int>::type = 0> Job each(JobContinuation func) const { eachInvariants(); return then(forEach(func)); } /** * Shorthand for a serialForEach loop that automatically uses the return type * of this job to deduce the type exepected. */ template::value, int>::type = 0> Job serialEach(JobContinuation func) const { eachInvariants(); return then(serialForEach(func)); } /** * Enable implicit conversion to Job. * * This is necessary in assignments that only use the return value (which is the normal case). * This avoids constructs like: * auto job = KAsync::start( ... ) * .then( ... ) * .then([](){}); //Necessary for the assignment without the implicit conversion */ template operator Job(); /** * Adds an unnamed value to the context. * The context is guaranteed to persist until the jobs execution has finished. * * Useful for setting smart pointer to manage lifetime of objects required * during the execution of the job. */ template Job &addToContext(const T &value) { assert(mExecutor); mExecutor->addToContext(QVariant::fromValue(value)); return *this; } /** * @brief Starts execution of the job chain. * * This will start the execution of the task chain, starting from the * first one. It is possible to call this function multiple times, each * invocation will start a new processing and provide a new Future to * watch its status. * * @param in Argument to be passed to the very first task * @return Future<Out> object which will contain result of the last * task once if finishes executing. See Future documentation for more details. * * @see exec(), Future */ template KAsync::Future exec(FirstIn in); /** * @brief Starts execution of the job chain. * * This will start the execution of the task chain, starting from the * first one. It is possible to call this function multiple times, each * invocation will start a new processing and provide a new Future to * watch its status. * * @return Future<Out> object which will contain result of the last * task once if finishes executing. See Future documentation for more details. * * @see exec(FirstIn in), Future */ KAsync::Future exec(); explicit Job(const JobContinuation &func); explicit Job(const HandleContinuation &func); private: //@cond PRIVATE explicit Job(Private::ExecutorBasePtr executor); template Job thenImpl(const Private::ContinuationHelper &helper, Private::ExecutionFlag execFlag = Private::ExecutionFlag::GoodCase) const; template Job syncThenImpl(const SyncContinuation &func, Private::ExecutionFlag execFlag = Private::ExecutionFlag::GoodCase) const; template Job syncThenImpl(const SyncErrorContinuation &func, Private::ExecutionFlag execFlag = Private::ExecutionFlag::Always) const; template void thenInvariants() const; //Base case for an empty parameter pack template typename std::enable_if<(sizeof...(InOther) == 0)>::type thenInvariants() const; template void eachInvariants() const; //@endcond }; } // namespace KAsync // out-of-line definitions of Job methods #include "job_impl.h" #endif // KASYNC_H