You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Update docs for io_result-based when_all/when_any signatures (#244)
All documentation and Antora example pages still described the old
API where when_all/when_any accepted plain task<T> and returned
tuples/variants without error_code. Update to match the current
io_result-aware combinators:
- when_all children must be io_task, result is io_result<R1,...,Rn>
- when_any result is variant<error_code, R1,...,Rn> with error at
index 0; only !ec wins
- Replace monostate references with tuple<> for void tasks
- Replace fictitious timeout_after examples with the real timeout
combinator
- Fix when_any docs that incorrectly described exceptions as valid
completions
Copy file name to clipboardExpand all lines: doc/modules/ROOT/pages/4.coroutines/4f.composition.adoc
+87-66Lines changed: 87 additions & 66 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -34,82 +34,95 @@ task<> concurrent()
34
34
35
35
== when_all: Wait for All Tasks
36
36
37
-
`when_all` launches multiple tasks concurrently and waits for all of them to complete:
37
+
`when_all` launches multiple `io_task` children concurrently and waits for all of them to complete. It returns `task<io_result<R1, R2, ..., Rn>>`, a single `ec` plus the flattened payloads:
auto [a, b, c] = co_await when_all(fetch_a(), fetch_b(), fetch_c());
50
-
49
+
auto [ec, a, b, c] = co_await when_all(fetch_a(), fetch_b(), fetch_c());
50
+
51
+
// ec == std::error_code{} (success)
51
52
// a == 1
52
53
// b == 2
53
54
// c == "hello"
54
55
}
55
56
----
56
57
57
-
=== Result Tuple
58
+
=== Result Type
58
59
59
-
`when_all` returns a tuple of results in the same order as the input tasks. Use structured bindings to unpack them.
60
+
`when_all` returns `io_result<R1, ..., Rn>` where each `Ri` is the child's payload flattened: `io_result<T>` contributes `T`, `io_result<>` contributes `tuple<>`. Check `ec` first; values are only meaningful when `!ec`.
60
61
61
-
=== Void Tasks
62
+
=== Void io_tasks
62
63
63
-
Tasks returning `void` contribute `std::monostate` to the result tuple, preserving the task-index-to-result-index mapping:
64
+
`io_task<>` children contribute `tuple<>` to the result:
@@ -124,23 +137,24 @@ When one task fails, `when_all` requests stop for its siblings. Well-behaved tas
124
137
125
138
[source,cpp]
126
139
----
127
-
task<> long_running()
140
+
io_task<> long_running()
128
141
{
129
-
auto token = co_await get_stop_token();
130
-
142
+
auto token = co_await this_coro::stop_token;
143
+
131
144
for (int i = 0; i < 1000; ++i)
132
145
{
133
146
if (token.stop_requested())
134
-
co_return; // Exit early when sibling fails
135
-
147
+
co_return io_result<>{}; // Exit early when sibling fails
148
+
136
149
co_await do_iteration();
137
150
}
151
+
co_return io_result<>{};
138
152
}
139
153
----
140
154
141
-
== when_any: First-to-Finish Wins
155
+
== when_any: First-to-Succeed Wins
142
156
143
-
`when_any` launches multiple tasks concurrently and returns when the *first* one completes:
157
+
`when_any` launches multiple `io_task` children concurrently and returns when the first one *succeeds* (`!ec`):
144
158
145
159
[source,cpp]
146
160
----
@@ -149,17 +163,19 @@ task<> long_running()
149
163
task<> example()
150
164
{
151
165
auto result = co_await when_any(
152
-
fetch_int(), // task<int>
153
-
fetch_string() // task<std::string>
166
+
fetch_int(), // io_task<int>
167
+
fetch_string() // io_task<std::string>
154
168
);
155
-
// result.index() indicates which task won (0 or 1)
156
-
// result is std::variant<int, std::string>
169
+
// result is std::variant<std::error_code, int, std::string>
170
+
// index 0: all tasks failed (error_code)
171
+
// index 1: fetch_int won
172
+
// index 2: fetch_string won
157
173
}
158
174
----
159
175
160
-
The result is a variant with one alternative per input task, preserving positional correspondence. Use `.index()` to determine the winner. When a winner is determined, stop is requested for all siblings. All tasks complete before `when_any` returns.
176
+
The result is a `variant` with `error_code` at index 0 (failure/no winner) and one alternative per input task at indices 1..N. Only tasks returning `!ec` can win; errors and exceptions do not count as winning. When a winner is found, stop is requested for all siblings. All tasks complete before `when_any` returns.
161
177
162
-
For detailed coverage including error handling, cancellation, and the vector overload, see Racing Tasks.
178
+
For detailed coverage including error handling, cancellation, and the range overload, see Racing Tasks.
// This requires a range-based when_all (not yet available)
203
-
// For now, use fixed-arity when_all
204
-
219
+
220
+
auto [ec, results] = co_await when_all(std::move(tasks));
221
+
if (ec)
222
+
co_return 0;
223
+
205
224
int total = 0;
206
-
// ... accumulate results
225
+
for (auto v : results)
226
+
total += v;
207
227
co_return total;
208
228
}
209
229
----
210
230
211
-
=== Timeout with Fallback
231
+
=== Timeout
212
232
213
-
Use `when_any` to implement timeout with fallback:
233
+
The `timeout` combinator races an awaitable against a deadline:
214
234
215
235
[source,cpp]
216
236
----
217
-
task<Response> fetch_with_timeout(Request req)
218
-
{
219
-
auto result = co_await when_any(
220
-
fetch_data(req),
221
-
timeout_after<Response>(100ms)
222
-
);
223
-
224
-
if (result.index() == 1)
225
-
throw timeout_error{"Request timed out"};
237
+
#include <boost/capy/timeout.hpp>
226
238
227
-
co_return std::get<0>(result);
239
+
task<> example()
240
+
{
241
+
auto [ec, n] = co_await timeout(sock.read_some(buf), 50ms);
242
+
if (ec == cond::timeout)
243
+
{
244
+
// deadline expired before read completed
245
+
}
228
246
}
229
247
----
230
248
231
-
The `timeout_after` helper waits for the specified duration then throws. If `fetch_data` completes first, its result is returned. If the timer wins, the timeout exception propagates.
249
+
`timeout` returns the same `io_result` type as the inner awaitable. On timeout, `ec` is set to `error::timeout` and payload values are default-initialized. Unlike `when_any`, exceptions from the inner awaitable are always propagated and never swallowed by the timer.
232
250
233
251
== Implementation Notes
234
252
@@ -262,6 +280,9 @@ This design ensures proper context propagation to all children.
262
280
263
281
| `<boost/capy/when_any.hpp>`
264
282
| First-completion racing with when_any
283
+
284
+
| `<boost/capy/timeout.hpp>`
285
+
| Race an awaitable against a deadline
265
286
|===
266
287
267
288
You have now learned how to compose tasks concurrently with `when_all` and `when_any`. In the next section, you will learn about frame allocators for customizing coroutine memory allocation.
0 commit comments