|
1 | 1 | .. ============================================================================= |
2 | | -.. Copyright 2023 NVIDIA Corporation |
3 | | -.. |
| 2 | +.. Copyright 2025 NVIDIA Corporation |
| 3 | +.. |
4 | 4 | .. Licensed under the Apache License, Version 2.0 (the "License"); |
5 | 5 | .. you may not use this file except in compliance with the License. |
6 | 6 | .. You may obtain a copy of the License at |
7 | | -.. |
| 7 | +.. |
8 | 8 | .. http://www.apache.org/licenses/LICENSE-2.0 |
9 | | -.. |
| 9 | +.. |
10 | 10 | .. Unless required by applicable law or agreed to in writing, software |
11 | 11 | .. distributed under the License is distributed on an "AS IS" BASIS, |
12 | 12 | .. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | 13 | .. See the License for the specific language governing permissions and |
14 | 14 | .. limitations under the License. |
15 | 15 | .. ============================================================================= |
16 | 16 |
|
| 17 | +.. highlight:: cpp |
| 18 | + :linenothreshold: 5 |
| 19 | + |
17 | 20 | Welcome to ``stdexec`` |
18 | 21 | ====================== |
19 | 22 |
|
20 | | -``stdexec`` is the reference implementation for `P2300 <https://wg21.link/P2300>`_, the |
21 | | -``std::execution`` proposal to the C++ standard library which will bring to C++26 a standard |
22 | | -async programming model. |
| 23 | +``stdexec`` is the reference implementation for `C++26's asynchronous execution framework |
| 24 | +<https://eel.is/c++draft/exec>`_, ``std::execution``. It provides a modern, |
| 25 | +composable, and efficient abstraction for asynchronous programming in C++. |
| 26 | + |
| 27 | +Sender/Receiver Abstraction in C++26 |
| 28 | +==================================== |
| 29 | + |
| 30 | +The **sender/receiver abstraction** is a foundational model for asynchronous programming in |
| 31 | +modern C++, proposed for standardization in **C++26** (originally targeted for C++23). It aims |
| 32 | +to unify and modernize asynchronous workflows across the C++ ecosystem. |
| 33 | + |
| 34 | +🔧 Motivation |
| 35 | +-------------- |
| 36 | + |
| 37 | +C++'s legacy async mechanisms — ``std::async``, futures, coroutines — have several limitations: |
| 38 | + |
| 39 | +- Inflexible and hard to compose. |
| 40 | + |
| 41 | +- Inefficient or heap-heavy. |
| 42 | + |
| 43 | +- Difficult to customize or extend. |
| 44 | + |
| 45 | +- Incompatible across libraries (e.g., Boost, Folly). |
| 46 | + |
| 47 | +The sender/receiver abstraction offers: |
| 48 | + |
| 49 | +- Composable async operations. |
| 50 | + |
| 51 | +- Customizable schedulers and execution strategies. |
| 52 | + |
| 53 | +- Clean cancellation handling. |
| 54 | + |
| 55 | +- Coroutine integration. |
| 56 | + |
| 57 | +- Zero-cost abstractions. |
| 58 | + |
| 59 | +🧱 Core Concepts |
| 60 | +---------------- |
| 61 | + |
| 62 | +1. Scheduler |
| 63 | +^^^^^^^^^^^^ |
| 64 | + |
| 65 | +A **scheduler** is an object that provides a way to schedule work. They are lightweight |
| 66 | +handles to what is often a heavy-weight and immovable **execution context**. Execution |
| 67 | +contexts are where work actually happens, and they can be anything that can execute code. |
| 68 | +Examples of execution contexts: |
| 69 | + |
| 70 | +- Thread pools. |
| 71 | +- Event loops. |
| 72 | +- GPUs. |
| 73 | +- An I/O subsystem. |
| 74 | +- Any other execution model. |
| 75 | + |
| 76 | +.. code-block:: cpp |
| 77 | +
|
| 78 | + auto sched = exec::get_parallel_scheduler(); // Obtain the default system scheduler |
| 79 | + auto sndr = stdexec::schedule(sched); // Create a sender from the scheduler |
| 80 | +
|
| 81 | +The sender you get back from ``stdexec::schedule`` is a factory for operations that, |
| 82 | +when started, will immediately call ``set_value()`` on the receiver the operation was |
| 83 | +constructed with. And crucially, it does so from the context of the scheduler. Work |
| 84 | +is executed on a context by chaining continuations to one of these senders, and passing |
| 85 | +it to one of the algorithms that starts work, like ``stdexec::sync_wait``. |
| 86 | + |
| 87 | +2. Sender |
| 88 | +^^^^^^^^^ |
| 89 | + |
| 90 | +A **sender** is an object that describes an asynchronous computation that may happen |
| 91 | +later. It can do nothing on its own, but when connected to a receiver, it returns an |
| 92 | +**operation state** that can start the work described by the sender. |
| 93 | + |
| 94 | +``stdexec`` provides a set of generic **sender algorithms** that can be used to chain |
| 95 | +operations together. There are also algorithms, like ``stdexec::sync_wait``, that can |
| 96 | +be used to launch the sender. The sender algorithms take care of connecting the sender |
| 97 | +to a receiver and managing the lifetime the operation state. |
| 98 | + |
| 99 | +- Produces values (or errors) asynchronously. |
| 100 | + |
| 101 | +- Can be requested to stop early. |
| 102 | + |
| 103 | +- Supports composition. |
| 104 | + |
| 105 | +- Is lazy (does nothing until connected and started). |
| 106 | + |
| 107 | +.. code-block:: cpp |
| 108 | +
|
| 109 | + auto sndr = stdexec::just(42); // Sender that yields 42 immediately |
| 110 | + auto [result] = stdexec::sync_wait(sndr).value(); // Start the work & wait for the result |
| 111 | + assert(result.value() == 42); |
| 112 | +
|
| 113 | +3. Receiver |
| 114 | +^^^^^^^^^^^ |
| 115 | + |
| 116 | +A **receiver** is an object that consumes the result of a sender. It defines three |
| 117 | +member functions that handle completion: |
| 118 | + |
| 119 | +- ``.set_value(args...)``: Called on success. |
| 120 | + |
| 121 | +- ``.set_error(err)``: Called on error. |
| 122 | + |
| 123 | +- ``.set_stopped()``: Called if the operation is canceled. |
| 124 | + |
| 125 | +.. code-block:: cpp |
| 126 | +
|
| 127 | + struct MyReceiver { |
| 128 | + using receiver_concept = stdexec::receiver_t; |
| 129 | +
|
| 130 | + void set_value(int v) noexcept { /* success */ } |
| 131 | + void set_error(std::exception_ptr e) noexcept { /* error */ } |
| 132 | + void set_stopped() noexcept { /* cancellation */ } |
| 133 | + }; |
| 134 | +
|
| 135 | +As a user, you typically won't deal with receivers directly. |
| 136 | +They are an implementation detail of sender algorithms. |
| 137 | + |
| 138 | +4. Operation State |
| 139 | +^^^^^^^^^^^^^^^^^^ |
| 140 | + |
| 141 | +Connecting a sender to a receiver yields an **operation state**, which: |
| 142 | + |
| 143 | +- Represents the in-progress computation. |
| 144 | + |
| 145 | +- Is started explicitly via ``.start()``. |
| 146 | + |
| 147 | +.. code-block:: cpp |
| 148 | +
|
| 149 | + auto op = stdexec::connect(sndr, MyReceiver{}); // Connect sender to receiver |
| 150 | + stdexec::start(op); // Start the operation |
| 151 | +
|
| 152 | +Operation states are immovable, and once started, they must be kept alive until the |
| 153 | +operation completes. As a user though, you typically will not manage them directly. |
| 154 | +They are handled by the sender algorithms. |
| 155 | + |
| 156 | +5. Environments |
| 157 | +^^^^^^^^^^^^^^^^^^ |
23 | 158 |
|
24 | | -It provides: |
| 159 | +Environments are a key concept in the sender/receiver model. An **environment** is an |
| 160 | +unordered collection of key/value pairs, queryable at runtime via tag types. Every |
| 161 | +receiver has a (possibly empty) environment that can be obtained by passing the receiver |
| 162 | +to ``stdexec::get_env()``. |
25 | 163 |
|
26 | | -* TODO |
| 164 | +Environments provide a way to pass contextual information like stop tokens, allocators, or |
| 165 | +schedulers to asynchronous operations. That information is then used by the operation to |
| 166 | +customize its behavior. |
| 167 | + |
| 168 | +🧮 Composition via Algorithms |
| 169 | +----------------------------- |
| 170 | + |
| 171 | +One benefit of the lazy evaluation of senders is that it makes it possible to create |
| 172 | +reusable algorithms that can be composed together. ``stdexec`` provides a rich set of |
| 173 | +algorithms that can be used to build complex asynchronous workflows. |
| 174 | + |
| 175 | +A **sender factory algorithm** creates a sender that can be used to start an operation. |
| 176 | +Below are some of the key sender factory algorithms provided by ``stdexec``: |
| 177 | + |
| 178 | +.. list-table:: Sender Factory Algorithms |
| 179 | + |
| 180 | + * - **CPO** |
| 181 | + - **Description** |
| 182 | + * - ``stdexec::schedule`` |
| 183 | + - Obtains a sender from a scheduler. |
| 184 | + * - ``stdexec::just`` |
| 185 | + - Creates a sender that will immediately complete with a set of values. |
| 186 | + * - ``stdexec::read_env`` |
| 187 | + - Reads a value from the receiver's environment and completes with it. |
| 188 | + |
| 189 | +A **sender adaptor algorithm** takes an existing sender (or several senders) and |
| 190 | +transforms it into a new sender with additional behavior. Below are some key sender |
| 191 | +adaptor algorithms. Check the :ref:`Reference` section for additional algorithms. |
| 192 | + |
| 193 | +.. list-table:: Sender Adaptor Algorithms |
| 194 | + :class: tight-table |
| 195 | + |
| 196 | + * - **CPO** |
| 197 | + - **Description** |
| 198 | + * - ``stdexec::then`` |
| 199 | + - Applies a function to the value from a sender. |
| 200 | + * - ``stdexec::starts_on`` |
| 201 | + - Executes an async operation on the specified scheduler. |
| 202 | + * - ``stdexec::continues_on`` |
| 203 | + - Executes an async operation on the current scheduler and then transfers |
| 204 | + execution to the specified scheduler. |
| 205 | + * - ``stdexec::on`` |
| 206 | + - Executes an async operation on a different scheduler and then transitions |
| 207 | + back to the original scheduler. |
| 208 | + * - ``stdexec::when_all`` |
| 209 | + - Combines multiple senders, making it possible to execute them in parallel. |
| 210 | + * - ``stdexec::let_value`` |
| 211 | + - Executes an async operation dynamically based on the results of a specified |
| 212 | + sender. |
| 213 | + * - ``stdexec::write_env`` |
| 214 | + - Writes a value to the receiver's environment, allowing it to be used by |
| 215 | + child operations. |
| 216 | + |
| 217 | +A **sender consumer algorithm** takes a sender connects it to a receiver and starts the |
| 218 | +resulting operation. Here are some key sender consumer algorithms: |
| 219 | + |
| 220 | +.. list-table:: Sender Consumer Algorithms |
| 221 | + |
| 222 | + * - **CPO** |
| 223 | + - **Description** |
| 224 | + * - ``stdexec::sync_wait`` |
| 225 | + - Blocks the calling thread until the sender completes and returns the result. |
| 226 | + * - ``stdexec::start_detached`` |
| 227 | + - Starts the operation without waiting for it to complete. |
| 228 | + |
| 229 | +Sender algorithms are defined in terms of **core customization points**. Below are the |
| 230 | +core customization points that define how senders and receivers interact: |
| 231 | + |
| 232 | +.. list-table:: Core customization points |
| 233 | + |
| 234 | + * - **CPO** |
| 235 | + - **Description** |
| 236 | + * - ``stdexec::connect`` |
| 237 | + - Connects a sender to a receiver resulting in an operation state. |
| 238 | + * - ``stdexec::start`` |
| 239 | + - Starts the operation. |
| 240 | + * - ``stdexec::set_value`` |
| 241 | + - Called by the operation state to deliver a value to the receiver. |
| 242 | + * - ``stdexec::set_error`` |
| 243 | + - Called by the operation state to deliver an error to the receiver. |
| 244 | + * - ``stdexec::set_stopped`` |
| 245 | + - Called by the operation state to indicate that the operation was stopped. |
| 246 | + * - ``stdexec::get_env`` |
| 247 | + - Retrieves the environment from a receiver. |
| 248 | + |
| 249 | +Here is an example of using sender algorithms to create a simple async pipeline: |
| 250 | + |
| 251 | +.. code-block:: cpp |
| 252 | +
|
| 253 | + // Create a sender that produces a value: |
| 254 | + auto pipeline = stdexec::just(42) |
| 255 | + // Transform the value using `then` on the specified scheduler: |
| 256 | + | stdexec::on(some_scheduler, stdexec::then([](int i) { return i * 2; })) |
| 257 | + // Further transform the value using `then` on the starting scheduler: |
| 258 | + | stdexec::then([](int i) { return i + 1; }); |
| 259 | + // Finally, wait for the result: |
| 260 | + auto [result] = stdexec::sync_wait(std::move(pipeline)).value(); |
| 261 | +
|
| 262 | +
|
| 263 | +🔄 Coroutine Integration |
| 264 | +------------------------ |
| 265 | + |
| 266 | +Senders can be ``co_await``-ed in coroutines if they model ``awaitable_sender``. Any sender |
| 267 | +that can complete successfully in exactly one way is an awaitable sender. |
| 268 | + |
| 269 | +.. code-block:: cpp |
| 270 | +
|
| 271 | + auto my_task() -> exec::task<int> { |
| 272 | + int x = co_await some_sender(); |
| 273 | + co_return x + 1; |
| 274 | + } |
| 275 | +
|
| 276 | +If a sender that is being ``co_await``-ed completes with an error, the coroutine will |
| 277 | +throw an exception. If it completes with a stop, the coroutine will be canceled. That is, |
| 278 | +the coroutine will never be resumed; rather, it and its calling coroutines will be |
| 279 | +destroyed. |
| 280 | + |
| 281 | +In addition, all awaitable types can be used as senders, allowing them to be composed with |
| 282 | +sender algorithms. |
| 283 | + |
| 284 | +This allows ergonomic, coroutine-based async programming with sender semantics under the |
| 285 | +hood. |
| 286 | + |
| 287 | +🚦 Standardization Status (as of 2025) |
| 288 | +-------------------------------------- |
| 289 | + |
| 290 | +- The core sender/receiver model has been accepted into the C++ standard for C++26. |
| 291 | + |
| 292 | +- Additional facilities have also been accepted such as |
| 293 | + |
| 294 | + * a system scheduler, |
| 295 | + * a sender-aware coroutine task type, |
| 296 | + * an async scope for spawning tasks dynamically. |
| 297 | + |
| 298 | +- Interop with networking is being explored for C++29. |
| 299 | + |
| 300 | +- Widely prototyped and tested in libraries and production settings. |
| 301 | + |
| 302 | +🚀 Benefits |
| 303 | +----------- |
| 304 | + |
| 305 | +- ✅ Zero-cost abstractions: No heap allocations or runtime overhead. |
| 306 | + |
| 307 | +- ✅ Composable: Express async pipelines clearly. |
| 308 | + |
| 309 | +- ✅ Customizable: Plug in your own schedulers, tokens, adapters. |
| 310 | + |
| 311 | +- ✅ Coroutine-friendly: Clean ``co_await`` support. |
| 312 | + |
| 313 | +- ✅ Unified async model: Works for I/O, compute, UI, etc. |
| 314 | + |
| 315 | +🔚 Summary |
| 316 | +----------- |
| 317 | + |
| 318 | +The sender/receiver abstraction: |
| 319 | + |
| 320 | +- Brings modern, composable async programming to C++. |
| 321 | + |
| 322 | +- Serves as a foundation for future concurrency features. |
| 323 | + |
| 324 | +- Enables high-performance, coroutine-friendly workflows. |
| 325 | + |
| 326 | +- Is set to become the standard async model in C++26. |
27 | 327 |
|
28 | 328 | .. toctree:: |
29 | 329 | :maxdepth: 2 |
30 | 330 | :caption: Contents: |
31 | 331 |
|
32 | | - reference/index |
33 | | - |
34 | 332 | user/index |
35 | 333 |
|
36 | 334 | developer/index |
37 | 335 |
|
38 | | -.. .. toctree: : |
39 | | -.. :maxdepth: 1 |
40 | | -
|
41 | | -.. versions |
| 336 | + reference/index |
42 | 337 |
|
43 | 338 | Indices and tables |
44 | 339 | ------------------ |
|
0 commit comments