KASync

async.h
1/*
2 SPDX-FileCopyrightText: 2014-2015 Daniel Vrátil <dvratil@redhat.com>
3 SPDX-FileCopyrightText: 2016 Daniel Vrátil <dvratil@kde.org>
4 SPDX-FileCopyrightText: 2016 Christian Mollekopf <mollekopf@kolabsystems.com>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#ifndef KASYNC_H
10#define KASYNC_H
11
12#include "kasync_export.h"
13
14#include <functional>
15#include <type_traits>
16#include <cassert>
17
18#include <QVariant>
19
20#include "future.h"
21#include "debug.h"
22
23#include "continuations_p.h"
24#include "executor_p.h"
25
26class QObject;
27
28/**
29 * @mainpage KAsync
30 *
31 * @brief API to help write async code.
32 *
33 * This API is based around jobs that take lambdas to execute asynchronous tasks.
34 * Each async operation can take a continuation that can then be used to execute
35 * further async operations. That way it is possible to build async chains of
36 * operations that can be stored and executed later on. Jobs can be composed,
37 * similarly to functions.
38 *
39 * Relations between the components:
40 * * Job: API wrapper around Executors chain. Can be destroyed while still running,
41 * because the actual execution happens in the background
42 * * Executor: Describes task to execute. Executors form a linked list matching the
43 * order in which they will be executed. The Executor chain is destroyed when
44 * the parent Job is destroyed. However if the Job is still running it is
45 * guaranteed that the Executor chain will not be destroyed until the execution
46 * is finished.
47 * * Execution: The running execution of the task stored in Executor. Each call to
48 * Job::exec() instantiates new Execution chain, which makes it possible for
49 * the Job to be executed multiple times (even in parallel).
50 * * Future: Representation of the result that is being calculated
51 *
52 *
53 * TODO: Possibility to abort a job through future (perhaps optional?)
54 * TODO: Support for timeout, specified during exec call, after which the error
55 * handler gets called with a defined errorCode.
56 */
57
58
59namespace KAsync {
60
61template<typename PrevOut, typename Out, typename ... In>
62class Executor;
63
64class JobBase;
65
66template<typename Out, typename ... In>
67class Job;
68
69//@cond PRIVATE
70namespace Private {
71
72template<typename Out, typename ... In>
73Job<Out, In ...> startImpl(Private::ContinuationHolder<Out, In ...> &&helper)
74{
75 static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed.");
76 return Job<Out, In...>(QSharedPointer<Private::Executor<Out, In ...>>::create(
77 std::forward<Private::ContinuationHolder<Out, In...>>(helper), nullptr, Private::ExecutionFlag::GoodCase));
78}
79
80} // namespace Private
81//@endcond
82
83
84/**
85 * @relates Job
86 *
87 * Start an asynchronous job sequence.
88 *
89 * start() is your starting point to build a chain of jobs to be executed
90 * asynchronously.
91 *
92 * @param func A continuation to be executed.
93 */
94
95///Sync continuation without job: [] () -> T { ... }
96template<typename Out = void, typename ... In, typename F>
97auto start(F &&func) -> std::enable_if_t<!std::is_base_of<JobBase, decltype(func(std::declval<In>() ...))>::value,
98 Job<decltype(func(std::declval<In>() ...)), In...>>
99{
100 static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed.");
101 return Private::startImpl<Out, In...>(Private::ContinuationHolder<Out, In ...>(SyncContinuation<Out, In ...>(std::forward<F>(func))));
102}
103
104///continuation with job: [] () -> KAsync::Job<...> { ... }
105template<typename Out = void, typename ... In, typename F>
106auto start(F &&func) -> std::enable_if_t<std::is_base_of<JobBase, decltype(func(std::declval<In>() ...))>::value,
107 Job<typename decltype(func(std::declval<In>() ...))::OutType, In...>>
108{
109 static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed.");
110 return Private::startImpl<Out, In...>(Private::ContinuationHolder<Out, In ...>(JobContinuation<Out, In...>(std::forward<F>(func))));
111}
112
113///Handle continuation: [] (KAsync::Future<T>, ...) { ... }
114template<typename Out = void, typename ... In>
115auto start(AsyncContinuation<Out, In ...> &&func) -> Job<Out, In ...>
116{
117 static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed.");
118 return Private::startImpl<Out, In...>(Private::ContinuationHolder<Out, In ...>(std::forward<AsyncContinuation<Out, In ...>>(func)));
119}
120
121enum ControlFlowFlag {
122 Break,
124};
125
126/**
127 * @relates Job
128 *
129 * Async while loop.
130 *
131 * Loop continues while body returns ControlFlowFlag::Continue.
132 */
133KASYNC_EXPORT Job<void> doWhile(const Job<ControlFlowFlag> &body);
134
135/**
136 * @relates Job
137 *
138 * Async while loop.
139 *
140 * Shorthand that takes a continuation.
141 *
142 * @see doWhile
143 */
144KASYNC_EXPORT Job<void> doWhile(const JobContinuation<ControlFlowFlag> &body);
145
146
147
148/**
149 * @relates Job
150 *
151 * Async delay.
152 */
153KASYNC_EXPORT Job<void> wait(int delay);
154
155/**
156 * @relates Job
157 *
158 * A null job.
159 *
160 * An async noop.
161 *
162 */
163template<typename Out = void>
164Job<Out> null();
165
166/**
167 * @relates Job
168 *
169 * Async value.
170 */
171template<typename Out>
172Job<Out> value(Out);
173
174/**
175 * @relates Job
176 *
177 * Async foreach loop.
178 *
179 * This will execute a job for every value in the list.
180 * Errors while not stop processing of other jobs but set an error on the wrapper job.
181 */
182template<typename List, typename ValueType = typename List::value_type>
183Job<void, List> forEach(KAsync::Job<void, ValueType> job);
184
185/**
186 * @relates Job
187 *
188 * Async foreach loop.
189 *
190 * Shorthand that takes a continuation.
191 *
192 * @see serialForEach
193 */
194template<typename List, typename ValueType = typename List::value_type>
195 Job<void, List> forEach(JobContinuation<void, ValueType> &&);
196
197
198/**
199 * @relates Job
200 *
201 * Serial Async foreach loop.
202 *
203 * This will execute a job for every value in the list sequentially.
204 * Errors while not stop processing of other jobs but set an error on the wrapper job.
205 */
206template<typename List, typename ValueType = typename List::value_type>
207Job<void, List> serialForEach(KAsync::Job<void, ValueType> job);
208
209/**
210 * @relates Job
211 *
212 * Serial Async foreach loop.
213 *
214 * Shorthand that takes a continuation.
215 *
216 * @see serialForEach
217 */
218template<typename List, typename ValueType = typename List::value_type>
219Job<void, List> serialForEach(JobContinuation<void, ValueType> &&);
220
221/**
222 * @brief Wait until all given futures are completed.
223 */
224template<template<typename> class Container>
225Job<void> waitForCompletion(Container<KAsync::Future<void>> &futures);
226
227/**
228 * @relates Job
229 *
230 * An error job.
231 *
232 * An async error.
233 *
234 */
235template<typename Out = void>
236Job<Out> error(int errorCode = 1, const QString &errorMessage = QString());
237
238/**
239 * @relates Job
240 *
241 * An error job.
242 *
243 * An async error.
244 *
245 */
246template<typename Out = void>
247Job<Out> error(const char *);
248
249/**
250 * @relates Job
251 *
252 * An error job.
253 *
254 * An async error.
255 *
256 */
257template<typename Out = void>
258Job<Out> error(const Error &);
259
260//@cond PRIVATE
261class KASYNC_EXPORT JobBase
262{
263 template<typename Out, typename ... In>
264 friend class Job;
265
266public:
267 explicit JobBase(const Private::ExecutorBasePtr &executor)
268 : mExecutor(executor)
269 {}
270
271 virtual ~JobBase() = default;
272
273protected:
274 Private::ExecutorBasePtr mExecutor;
275};
276//@endcond
277
278/**
279 * @brief An Asynchronous job
280 *
281 * A single instance of Job represents a single method that will be executed
282 * asynchronously. The Job is started by exec(), which returns Future
283 * immediately. The Future will be set to finished state once the asynchronous
284 * task has finished. You can use Future::waitForFinished() to wait for
285 * for the Future in blocking manner.
286 *
287 * It is possible to chain multiple Jobs one after another in different fashion
288 * (sequential, parallel, etc.). Calling exec() will then return a pending
289 * Future, and will execute the entire chain of jobs.
290 *
291 * @code
292 * auto job = Job::start<QList<int>>(
293 * [](KAsync::Future<QList<int>> &future) {
294 * MyREST::PendingUsers *pu = MyREST::requestListOfUsers();
295 * QObject::connect(pu, &PendingOperation::finished,
296 * [&](PendingOperation *pu) {
297 * future->setValue(dynamic_cast<MyREST::PendingUsers*>(pu)->userIds());
298 * future->setFinished();
299 * });
300 * })
301 * .each<QList<MyREST::User>, int>(
302 * [](const int &userId, KAsync::Future<QList<MyREST::User>> &future) {
303 * MyREST::PendingUser *pu = MyREST::requestUserDetails(userId);
304 * QObject::connect(pu, &PendingOperation::finished,
305 * [&](PendingOperation *pu) {
306 * future->setValue(Qlist<MyREST::User>() << dynamic_cast<MyREST::PendingUser*>(pu)->user());
307 * future->setFinished();
308 * });
309 * });
310 *
311 * KAsync::Future<QList<MyREST::User>> usersFuture = job.exec();
312 * usersFuture.waitForFinished();
313 * QList<MyRest::User> users = usersFuture.value();
314 * @endcode
315 *
316 * In the example above, calling @p job.exec() will first invoke the first job,
317 * which will retrieve a list of IDs and then will invoke the second function
318 * for each single entry in the list returned by the first function.
319 */
320template<typename Out, typename ... In>
321class [[nodiscard]] Job : public JobBase
322{
323 //@cond PRIVATE
324 template<typename OutOther, typename ... InOther>
325 friend class Job;
326
327 template<typename OutOther, typename ... InOther>
328 friend Job<OutOther, InOther ...> Private::startImpl(Private::ContinuationHolder<OutOther, InOther ...> &&);
329
330 template<typename List, typename ValueType>
331 friend Job<void, List> forEach(KAsync::Job<void, ValueType> job);
332
333 template<typename List, typename ValueType>
334 friend Job<void, List> serialForEach(KAsync::Job<void, ValueType> job);
335
336 // Used to disable implicit conversion of Job<void to Job<void> which triggers
337 // comiler warning.
338 struct IncompleteType;
339 //@endcond
340
341public:
342 typedef Out OutType;
343
344 ///A continuation
345 template<typename OutOther, typename ... InOther>
346 Job<OutOther, In ...> then(const Job<OutOther, InOther ...> &job) const;
347
348 ///Shorthands for a job that returns another job from it's continuation
349 //
350 //OutOther and InOther are only there fore backwards compatibility, but are otherwise ignored.
351 //It should never be necessary to specify any template arguments, as they are automatically deduced from the provided argument.
352 //
353 //We currently have to write a then overload for:
354 //* One argument in the continuation
355 //* No argument in the continuation
356 //* One argument + error in the continuation
357 //* No argument + error in the continuation
358 //This is due to how we extract the return type with "decltype(func(std::declval<Out>()))".
359 //Ideally we could conflate this into at least fewer overloads, but I didn't manage so far and this at least works as expected.
360
361 ///Continuation returning job: [] (Arg) -> KAsync::Job<...> { ... }
362 template<typename OutOther = void, typename ... InOther, typename F>
363 auto then(F &&func) const -> std::enable_if_t<std::is_base_of<JobBase, decltype(func(std::declval<Out>()))>::value,
364 Job<typename decltype(func(std::declval<Out>()))::OutType, In...>>
365 {
366 using ResultJob = decltype(func(std::declval<Out>())); //Job<QString, int>
367 return thenImpl<typename ResultJob::OutType, Out>(
368 {JobContinuation<typename ResultJob::OutType, Out>(std::forward<F>(func))}, Private::ExecutionFlag::GoodCase);
369 }
370
371 ///Void continuation with job: [] () -> KAsync::Job<...> { ... }
372 template<typename OutOther = void, typename ... InOther, typename F>
373 auto then(F &&func) const -> std::enable_if_t<std::is_base_of<JobBase, decltype(func())>::value,
374 Job<typename decltype(func())::OutType, In...>>
375 {
376 using ResultJob = decltype(func()); //Job<QString, void>
377 return thenImpl<typename ResultJob::OutType>(
378 {JobContinuation<typename ResultJob::OutType>(std::forward<F>(func))}, Private::ExecutionFlag::GoodCase);
379 }
380
381 ///Error continuation returning job: [] (KAsync::Error, Arg) -> KAsync::Job<...> { ... }
382 template<typename OutOther = void, typename ... InOther, typename F>
383 auto then(F &&func) const -> std::enable_if_t<std::is_base_of<JobBase, decltype(func(KAsync::Error{}, std::declval<Out>()))>::value,
384 Job<typename decltype(func(KAsync::Error{}, std::declval<Out>()))::OutType, In...>>
385 {
386 using ResultJob = decltype(func(KAsync::Error{}, std::declval<Out>())); //Job<QString, int>
387 return thenImpl<typename ResultJob::OutType, Out>(
388 {JobErrorContinuation<typename ResultJob::OutType, Out>(std::forward<F>(func))}, Private::ExecutionFlag::Always);
389 }
390
391 ///Error void continuation returning job: [] (KAsync::Error) -> KAsync::Job<...> { ... }
392 template<typename OutOther = void, typename ... InOther, typename F>
393 auto then(F &&func) const -> std::enable_if_t<std::is_base_of<JobBase, decltype(func(KAsync::Error{}))>::value,
394 Job<typename decltype(func(KAsync::Error{}))::OutType, In...>>
395 {
396 using ResultJob = decltype(func(KAsync::Error{}));
397 return thenImpl<typename ResultJob::OutType>(
398 {JobErrorContinuation<typename ResultJob::OutType>(std::forward<F>(func))}, Private::ExecutionFlag::Always);
399 }
400
401 ///Sync continuation: [] (Arg) -> void { ... }
402 template<typename OutOther = void, typename ... InOther, typename F>
403 auto then(F &&func) const -> std::enable_if_t<!std::is_base_of<JobBase, decltype(func(std::declval<Out>()))>::value,
404 Job<decltype(func(std::declval<Out>())), In...>>
405 {
406 using ResultType = decltype(func(std::declval<Out>())); //QString
407 return thenImpl<ResultType, Out>(
408 {SyncContinuation<ResultType, Out>(std::forward<F>(func))}, Private::ExecutionFlag::GoodCase);
409 }
410
411 ///Sync void continuation: [] () -> void { ... }
412 template<typename OutOther = void, typename ... InOther, typename F>
413 auto then(F &&func) const -> std::enable_if_t<!std::is_base_of<JobBase, decltype(func())>::value,
414 Job<decltype(func()), In...>>
415 {
416 using ResultType = decltype(func()); //QString
417 return thenImpl<ResultType>(
418 {SyncContinuation<ResultType>(std::forward<F>(func))}, Private::ExecutionFlag::GoodCase);
419 }
420
421 ///Sync error continuation: [] (KAsync::Error, Arg) -> void { ... }
422 template<typename OutOther = void, typename ... InOther, typename F>
423 auto then(F &&func) const -> std::enable_if_t<!std::is_base_of<JobBase, decltype(func(KAsync::Error{}, std::declval<Out>()))>::value,
424 Job<decltype(func(KAsync::Error{}, std::declval<Out>())),In...>>
425 {
426 using ResultType = decltype(func(KAsync::Error{}, std::declval<Out>())); //QString
427 return thenImpl<ResultType, Out>(
428 {SyncErrorContinuation<ResultType, Out>(std::forward<F>(func))}, Private::ExecutionFlag::Always);
429 }
430
431 ///Sync void error continuation: [] (KAsync::Error) -> void { ... }
432 template<typename OutOther = void, typename ... InOther, typename F>
433 auto then(F &&func) const -> std::enable_if_t<!std::is_base_of<JobBase, decltype(func(KAsync::Error{}))>::value,
434 Job<decltype(func(KAsync::Error{})), In...>>
435 {
436 using ResultType = decltype(func(KAsync::Error{}));
437 return thenImpl<ResultType>(
438 {SyncErrorContinuation<ResultType>(std::forward<F>(func))}, Private::ExecutionFlag::Always);
439 }
440
441 ///Shorthand for a job that receives the error and a handle
442 template<typename OutOther, typename ... InOther>
443 Job<OutOther, In ...> then(AsyncContinuation<OutOther, InOther ...> &&func) const
444 {
445 return thenImpl<OutOther, InOther ...>({std::forward<AsyncContinuation<OutOther, InOther ...>>(func)},
446 Private::ExecutionFlag::GoodCase);
447 }
448
449 ///Shorthand for a job that receives the error and a handle
450 template<typename OutOther, typename ... InOther>
451 Job<OutOther, In ...> then(AsyncErrorContinuation<OutOther, InOther ...> &&func) const
452 {
453 return thenImpl<OutOther, InOther ...>({std::forward<AsyncErrorContinuation<OutOther, InOther ...>>(func)}, Private::ExecutionFlag::Always);
454 }
455
456 ///Shorthand for a job that receives the error only
457 Job<Out, In ...> onError(SyncErrorContinuation<void> &&errorFunc) const;
458
459 /**
460 * Shorthand for a forEach loop that automatically uses the return type of
461 * this job to deduce the type expected.
462 */
463 template<typename OutOther = void, typename ListType = Out, typename ValueType = typename ListType::value_type, std::enable_if_t<!std::is_void<ListType>::value, int> = 0>
464 Job<void, In ...> each(JobContinuation<void, ValueType> &&func) const
465 {
466 eachInvariants<OutOther>();
467 return then<void, In ...>(forEach<Out, ValueType>(std::forward<JobContinuation<void, ValueType>>(func)));
468 }
469
470 /**
471 * Shorthand for a serialForEach loop that automatically uses the return type
472 * of this job to deduce the type expected.
473 */
474 template<typename OutOther = void, typename ListType = Out, typename ValueType = typename ListType::value_type, std::enable_if_t<!std::is_void<ListType>::value, int> = 0>
475 Job<void, In ...> serialEach(JobContinuation<void, ValueType> &&func) const
476 {
477 eachInvariants<OutOther>();
478 return then<void, In ...>(serialForEach<Out, ValueType>(std::forward<JobContinuation<void, ValueType>>(func)));
479 }
480
481 /**
482 * Enable implicit conversion to Job<void>.
483 *
484 * This is necessary in assignments that only use the return value (which is the normal case).
485 * This avoids constructs like:
486 * auto job = KAsync::start<int>( ... )
487 * .then<void, int>( ... )
488 * .then<void>([](){}); //Necessary for the assignment without the implicit conversion
489 */
490 template<typename ... InOther>
491 operator std::conditional_t<std::is_void<OutType>::value, IncompleteType, Job<void>>();
492
493 /**
494 * Adds an unnamed value to the context.
495 * The context is guaranteed to persist until the jobs execution has finished.
496 *
497 * Useful for setting smart pointer to manage lifetime of objects required
498 * during the execution of the job.
499 */
500 template<typename T>
501 Job<Out, In ...> &addToContext(const T &value)
502 {
503 assert(mExecutor);
504 mExecutor->addToContext(QVariant::fromValue<T>(value));
505 return *this;
506 }
507
508 /**
509 * Adds a guard.
510 * It is guaranteed that no callback is executed after the guard vanishes.
511 *
512 * Use this i.e. ensure you don't call-back into an already destroyed object.
513 */
514 Job<Out, In ...> &guard(const QObject *o)
515 {
516 assert(mExecutor);
517 mExecutor->guard(o);
518 return *this;
519 }
520
521 /**
522 * @brief Starts execution of the job chain.
523 *
524 * This will start the execution of the task chain, starting from the
525 * first one. It is possible to call this function multiple times, each
526 * invocation will start a new processing and provide a new Future to
527 * watch its status.
528 *
529 * @param in Argument to be passed to the very first task
530 * @return Future&lt;Out&gt; object which will contain result of the last
531 * task once if finishes executing. See Future documentation for more details.
532 *
533 * @see exec(), Future
534 */
535 template<typename FirstIn>
536 KAsync::Future<Out> exec(FirstIn in);
537
538 /**
539 * @brief Starts execution of the job chain.
540 *
541 * This will start the execution of the task chain, starting from the
542 * first one. It is possible to call this function multiple times, each
543 * invocation will start a new processing and provide a new Future to
544 * watch its status.
545 *
546 * @return Future&lt;Out&gt; object which will contain result of the last
547 * task once if finishes executing. See Future documentation for more details.
548 *
549 * @see exec(FirstIn in), Future
550 */
551 KAsync::Future<Out> exec();
552
553 explicit Job(JobContinuation<Out, In ...> &&func);
554 explicit Job(AsyncContinuation<Out, In ...> &&func);
555
556private:
557 //@cond PRIVATE
558 explicit Job(Private::ExecutorBasePtr executor);
559
560 template<typename OutOther, typename ... InOther>
561 Job<OutOther, In ...> thenImpl(Private::ContinuationHolder<OutOther, InOther ...> helper,
562 Private::ExecutionFlag execFlag = Private::ExecutionFlag::GoodCase) const;
563
564 template<typename InOther, typename ... InOtherTail>
565 void thenInvariants() const;
566
567 //Base case for an empty parameter pack
568 template<typename ... InOther>
569 auto thenInvariants() const -> std::enable_if_t<(sizeof...(InOther) == 0)>;
570
571 template<typename OutOther>
572 void eachInvariants() const;
573 //@endcond
574};
575
576} // namespace KAsync
577
578
579// out-of-line definitions of Job methods
580#include "job_impl.h"
581
582#endif // KASYNC_H
Q_SCRIPTABLE Q_NOREPLY void start()
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
STL namespace.
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:57 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.