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