Aria 3.2.6
C++23 MVVM framework — reactive, coroutine-first, ABI-layered
Loading...
Searching...
No Matches
task.hpp
Go to the documentation of this file.
1#pragma once
2
3// C++ coroutine Task<T> (C++20 and C++23): a lazy, single-shot, awaitable that returns T (or void).
4//
5// Task<int> compute() { co_return 42; }
6// Task<void> log() { co_return; }
7//
8// The task is started when co_awaited (or by Task::start()).
9// Exceptions thrown in the coroutine body are stored and rethrown by co_await.
10
11#include <coroutine>
12#include <exception>
13#include <optional>
14#include <stdexcept>
15#include <type_traits>
16#include <utility>
17
18namespace aria::async {
19
20template<typename T = void>
21class Task;
22
23namespace detail {
24
25template<typename T>
26struct TaskPromiseBase {
27 std::coroutine_handle<> continuation;
28 std::exception_ptr exception;
29
30 // Detached tasks run through final suspend so the compiler destroys
31 // their frames. Owned tasks remain suspended for their Task/awaiter.
32 bool detached = false;
33
34 auto initial_suspend() noexcept { return std::suspend_always{}; }
35
36 struct FinalAwaiter {
37 bool detached;
38
39 bool await_ready() const noexcept { return detached; }
40
41 // Templated to accept any coroutine_handle whose promise inherits TaskPromiseBase<T>
42 template<typename Promise>
43 std::coroutine_handle<> await_suspend(std::coroutine_handle<Promise> h) noexcept {
44 TaskPromiseBase<T>& base = h.promise();
45 return base.continuation ? base.continuation : std::noop_coroutine();
46 }
47
48 void await_resume() const noexcept {}
49 };
50
51 auto final_suspend() noexcept { return FinalAwaiter{detached}; }
52
53 void unhandled_exception() noexcept { exception = std::current_exception(); }
54};
55
56template<typename T>
57struct TaskPromise : TaskPromiseBase<T> {
58 using value_type = T;
59 std::optional<T> value;
60
61 Task<T> get_return_object() noexcept;
62
63 template<typename U>
64 void return_value(U&& v) noexcept(std::is_nothrow_constructible_v<T, U>) {
65 value.emplace(std::forward<U>(v));
66 }
67};
68
69template<>
70struct TaskPromise<void> : TaskPromiseBase<void> {
71 using value_type = void;
72 Task<void> get_return_object() noexcept;
73 void return_void() noexcept {}
74};
75
76} // namespace detail
77
78template<typename T>
79class [[nodiscard]] Task {
80public:
81 using promise_type = detail::TaskPromise<T>;
82 using handle_type = std::coroutine_handle<promise_type>;
83
84 Task() noexcept = default;
85 explicit Task(handle_type h) noexcept : handle_(h) {}
86
87 Task(const Task&) = delete;
88 Task& operator=(const Task&) = delete;
89
90 Task(Task&& o) noexcept : handle_(std::exchange(o.handle_, {})) {}
91 Task& operator=(Task&& o) noexcept {
92 if (this != &o) {
93 if (handle_) handle_.destroy();
94 handle_ = std::exchange(o.handle_, {});
95 }
96 return *this;
97 }
98
100 if (handle_) handle_.destroy();
101 }
102
103 [[nodiscard]] bool done() const noexcept { return handle_ && handle_.done(); }
104
110 auto operator co_await() && noexcept {
111 struct Awaiter {
112 handle_type h;
113
114 bool await_ready() const noexcept { return !h || h.done(); }
115
116 std::coroutine_handle<> await_suspend(std::coroutine_handle<> caller) noexcept {
117 h.promise().continuation = caller;
118 return h;
119 }
120
121 T await_resume() {
122 if (!h) { throw std::runtime_error("Task: empty handle"); }
123 auto& p = h.promise();
124 if (p.exception) std::rethrow_exception(p.exception);
125 if constexpr (!std::is_void_v<T>) {
126 return std::move(*p.value);
127 }
128 }
129
130 // Awaiter takes ownership: destroy when done.
131 ~Awaiter() { if (h) h.destroy(); }
132
133 Awaiter(handle_type hh) noexcept : h(hh) {}
134 Awaiter(Awaiter&& o) noexcept : h(std::exchange(o.h, {})) {}
135 Awaiter& operator=(Awaiter&&) = delete;
136 Awaiter(const Awaiter&) = delete;
137 Awaiter& operator=(const Awaiter&) = delete;
138 };
139 // Transfer ownership from the temporary `*this` to the Awaiter.
140 return Awaiter{std::exchange(handle_, {})};
141 }
142
144 void start() {
145 if (handle_ && !handle_.done()) handle_.resume();
146 }
147
158 void start_detached() && {
159 if (!handle_) return;
160 auto h = std::exchange(handle_, {});
161 if (h.done()) {
162 h.destroy();
163 return;
164 }
165 h.promise().detached = true;
166 // Completion may release the frame before resume() returns.
167 // If the body suspends, its eventual resumer drives completion.
168 // Never inspect h after resuming it.
169 h.resume();
170 }
171
173 void start_detached_() { std::move(*this).start_detached(); }
174
178 if (!handle_) throw std::runtime_error("Task: empty handle");
179 if (!handle_.done()) handle_.resume();
180 if (!handle_.done()) throw std::runtime_error("Task: did not complete synchronously");
181 auto& p = handle_.promise();
182 if (p.exception) std::rethrow_exception(p.exception);
183 if constexpr (!std::is_void_v<T>) {
184 return std::move(*p.value);
185 }
186 }
187
188private:
189 handle_type handle_{};
190};
191namespace detail {
192
193template<typename T>
194Task<T> TaskPromise<T>::get_return_object() noexcept {
195 return Task<T>{std::coroutine_handle<TaskPromise<T>>::from_promise(*this)};
196}
197
198inline Task<void> TaskPromise<void>::get_return_object() noexcept {
199 return Task<void>{std::coroutine_handle<TaskPromise<void>>::from_promise(*this)};
200}
201
202} // namespace detail
203
204} // namespace aria::async
Definition task.hpp:79
~Task()
Definition task.hpp:99
void start_detached_()
Lvalue convenience used internally by AsyncCommand.
Definition task.hpp:173
T blocking_get()
Blocking accessor — only safe if the task body is synchronous (no real async).
Definition task.hpp:177
std::coroutine_handle< promise_type > handle_type
Definition task.hpp:82
Task & operator=(const Task &)=delete
bool done() const noexcept
Definition task.hpp:103
Task & operator=(Task &&o) noexcept
Definition task.hpp:91
Task(const Task &)=delete
Task() noexcept=default
void start_detached() &&
Start the task and detach it: the coroutine frame stays alive until the coroutine completes,...
Definition task.hpp:158
detail::TaskPromise< T > promise_type
Definition task.hpp:81
void start()
Eagerly start the task without awaiting it. Use blocking_get() if you need the result.
Definition task.hpp:144
Task(Task &&o) noexcept
Definition task.hpp:90
Definition async_command.hpp:118