1.. _module-pw_async2-quickstart-guides: 2 3=================== 4Quickstart & guides 5=================== 6.. pigweed-module-subpage:: 7 :name: pw_async2 8 9.. _module-pw_async2-quickstart: 10 11---------- 12Quickstart 13---------- 14.. _//pw_async2/examples/count.cc: https://cs.opensource.google/pigweed/pigweed/+/main:pw_async2/examples/count.cc 15.. _//pw_async2/examples/BUILD.bazel: https://cs.opensource.google/pigweed/pigweed/+/main:pw_async2/examples/BUILD.bazel 16.. _//pw_async2/examples/BUILD.gn: https://cs.opensource.google/pigweed/pigweed/+/main:pw_async2/examples/BUILD.gn 17 18This quickstart outlines the general workflow for integrating ``pw_async2`` 19into a project. It's based on the following files in upstream Pigweed: 20 21* `//pw_async2/examples/count.cc`_ 22* `//pw_async2/examples/BUILD.bazel`_ 23* `//pw_async2/examples/BUILD.gn`_ 24 25The example app can be built and run in upstream Pigweed with the 26following command: 27 28.. code-block:: sh 29 30 bazelisk run //pw_async2/examples:count --config=cxx20 31 32.. _module-pw_async2-quickstart-rules: 33 341. Set up build rules 35===================== 36All ``pw_async2`` projects must add a dependency on the ``dispatcher`` target. 37This target defines the :cpp:class:`pw::async2::Task` class, an asynchronous 38unit of work analogous to a thread, as well as the 39:cpp:class:`pw::async2::Dispatcher` class, an event loop used to run ``Task`` 40instances to completion. 41 42.. tab-set:: 43 44 .. tab-item:: Bazel 45 46 Add a dependency on ``@pigweed//pw_async2:dispatcher`` in ``BUILD.bazel``: 47 48 .. literalinclude:: examples/BUILD.bazel 49 :language: py 50 :linenos: 51 :emphasize-lines: 10 52 :start-after: count-example-start 53 :end-before: count-example-end 54 55 .. tab-item:: GN 56 57 Add a dependency on ``$dir_pw_async2:dispatcher`` in ``BUILD.gn``: 58 59 .. literalinclude:: examples/BUILD.gn 60 :language: py 61 :linenos: 62 :emphasize-lines: 7 63 :start-after: count-example-start 64 :end-before: count-example-end 65 66.. _module-pw_async2-quickstart-dependencies: 67 682. Inject dependencies 69====================== 70Interfaces which wish to add new tasks to the event loop should accept and 71store a ``Dispatcher&`` reference. 72 73.. literalinclude:: examples/count.cc 74 :language: cpp 75 :linenos: 76 :start-after: examples-constructor-start 77 :end-before: examples-constructor-end 78 79This allows the interface to call ``dispatcher->Post(some_task)`` in order to 80run asynchronous work on the dispatcher's event loop. 81 82.. _module-pw_async2-quickstart-oneshot: 83 843. Post one-shot work to the dispatcher 85======================================= 86Simple, one-time work can be queued on the dispatcher via 87:cpp:func:`pw::async2::EnqueueHeapFunc`. 88 89.. _module-pw_async2-quickstart-tasks: 90 914. Post tasks to the dispatcher 92=============================== 93Async work that involves a series of asynchronous operations should be 94made into a task. This can be done by either implementing a custom task 95(see :ref:`module-pw_async2-guides-implementing-tasks`) or 96by writing a C++20 coroutine (see :cpp:class:`pw::async2::Coro`) and storing it 97in a :cpp:class:`pw::async2::CoroOrElseTask`. 98 99.. literalinclude:: examples/count.cc 100 :language: cpp 101 :linenos: 102 :start-after: examples-task-start 103 :end-before: examples-task-end 104 105The resulting task must either be stored somewhere that has a lifetime longer 106than the async operations (such as in a static or as a member of a long-lived 107class) or dynamically allocated using :cpp:func:`pw::async2::AllocateTask`. 108 109Finally, the interface instructs the dispatcher to run the task by invoking 110:cpp:func:`pw::async2::Dispatcher::Post`. 111 112See `//pw_async2/examples/count.cc`_ to view the complete example. 113 114.. _module-pw_async2-quickstart-toolchain: 115 1165. Build with an appropriate toolchain 117====================================== 118If using coroutines, remember to build your project with a toolchain 119that supports C++20 at minimum (the first version of C++ with coroutine 120support). For example, in upstream Pigweed a ``--config=cxx20`` must be 121provided when building and running the example: 122 123.. tab-set:: 124 125 .. tab-item:: Bazel 126 127 .. code-block:: sh 128 129 bazelisk build //pw_async2/examples:count --config=cxx20 130 131Other examples 132============== 133.. _quickstart/bazel: https://cs.opensource.google/pigweed/quickstart/bazel 134.. _//apps/blinky/: https://cs.opensource.google/pigweed/quickstart/bazel/+/main:apps/blinky/ 135.. _//modules/blinky/: https://cs.opensource.google/pigweed/quickstart/bazel/+/main:modules/blinky/ 136 137To see another example of ``pw_async2`` working in a minimal project, 138check out the following directories of Pigweed's `quickstart/bazel`_ repo: 139 140* `//apps/blinky/`_ 141* `//modules/blinky/`_ 142 143.. _module-pw_async2-guides: 144 145------ 146Guides 147------ 148 149.. _module-pw_async2-guides-implementing-tasks: 150 151Implementing tasks 152================== 153:cpp:class:`pw::async2::Task` instances complete one or more asynchronous 154operations. They are the top-level "thread" primitives of ``pw_async2``. 155 156You can use one of the concrete subclasses of ``Task`` that Pigweed provides: 157 158* :cpp:class:`pw::async2::CoroOrElseTask`: Delegates to a provided 159 coroutine and executes an ``or_else`` handler function on failure. 160* :cpp:class:`pw::async2::PendFuncTask`: Delegates to a provided 161 function. 162* :cpp:class:`pw::async2::PendableAsTask`: Delegates to a type 163 with a :cpp:func:`pw::async2::Pend` method. 164* :cpp:func:`pw::async2::AllocateTask`: Creates a concrete subclass of 165 ``Task``, just like ``PendableAsTask``, but the created task is 166 dynamically allocated and frees the associated memory upon 167 completion. 168 169Or you can subclass ``Task`` yourself. See :cpp:class:`pw::async2::Task` 170for more guidance on subclassing. 171 172.. _module-pw_async2-guides-tasks: 173 174How a dispatcher manages tasks 175============================== 176The purpose of a :cpp:class:`pw::async2::Dispatcher` is to keep track of a set 177of :cpp:class:`pw::async2::Task` objects and run them to completion. The 178dispatcher is essentially a scheduler for cooperatively-scheduled 179(non-preemptive) threads (tasks). 180 181While a dispatcher is running, it waits for one or more tasks to waken and then 182advances each task by invoking its :cpp:func:`pw::async2::Task::DoPend` method. 183The ``DoPend`` method is typically implemented manually by users, though it is 184automatically provided by coroutines. 185 186If the task is able to complete, ``DoPend`` will return ``Ready``, in which case 187the task is then deregistered from the dispatcher. 188 189If the task is unable to complete, ``DoPend`` must return ``Pending`` and arrange 190for the task to be woken up when it is able to make progress again. Once the 191task is rewoken, the task is re-added to the ``Dispatcher`` queue. The 192dispatcher will then invoke ``DoPend`` once more, continuing the cycle until 193``DoPend`` returns ``Ready`` and the task is completed. 194 195The following sequence diagram summarizes the basic workflow: 196 197.. mermaid:: 198 199 sequenceDiagram 200 participant e as External Event e.g. Interrupt 201 participant d as Dispatcher 202 participant t as Task 203 e->>t: Init Task 204 e->>d: Register task via Dispatcher::Post(Task) 205 d->>d: Add task to queue 206 d->>t: Run task via Task::DoPend() 207 t->>t: Task is waiting for data and can't yet complete 208 t->>e: Arrange for rewake via PW_ASYNC_STORE_WAKER 209 t->>d: Indicate that task is not complete via Pending() 210 d->>d: Remove task from queue 211 d->>d: Go to sleep because task queue is empty 212 e->>e: The data that the task needs has arrived 213 e->>d: Rewake via Waker::Wake() 214 d->>d: Re-add task to queue 215 d->>t: Run task via Task::DoPend() 216 t->>t: Task runs to completion 217 t->>d: Indicate that task is complete via Ready() 218 d->>d: Deregister the task 219 220.. _module-pw_async2-guides-pendables: 221 222Implementing invariants for pendable functions 223============================================== 224.. _invariants: https://stackoverflow.com/a/112088 225 226Any ``Pend``-like function or method similar to 227:cpp:func:`pw::async2::Task::DoPend` that can pause when it's not able 228to make progress on its task is known as a **pendable function**. When 229implementing a pendable function, make sure that you always uphold the 230following `invariants`_: 231 232* :ref:`module-pw_async2-guides-pendables-incomplete` 233* :ref:`module-pw_async2-guides-pendables-complete` 234 235.. note:: Exactly which APIs are considered pendable? 236 237 If it has the signature ``(Context&, ...) -> Poll<T>``, 238 then it's a pendable function. 239 240.. _module-pw_async2-guides-pendables-incomplete: 241 242Arranging future completion of incomplete tasks 243----------------------------------------------- 244When your pendable function can't yet complete: 245 246#. Do one of the following to make sure the task rewakes when it's ready to 247 make more progress: 248 249 * Delegate waking to a subtask. Arrange for that subtask's 250 pendable function to wake this task when appropriate. 251 252 * Arrange an external wakeup. Use :c:macro:`PW_ASYNC_STORE_WAKER` 253 to store the task's waker somewhere, and then call 254 :cpp:func:`pw::async2::Waker::Wake` from an interrupt or another thread 255 once the event that the task is waiting for has completed. 256 257 * Re-enqueue the task with :cpp:func:`pw::async2::Context::ReEnqueue`. 258 This is a rare case. Usually, you should just create an immediately 259 invoked ``Waker``. 260 261#. Make sure to return :cpp:type:`pw::async2::Pending` to signal that the task 262 is incomplete. 263 264In other words, whenever your pendable function returns 265:cpp:type:`pw::async2::Pending`, you must guarantee that 266:cpp:func:`pw::async2::Context::Wake` is called once in the future. 267 268For example, one implementation of a delayed task might arrange for its ``Waker`` 269to be woken by a timer once some time has passed. Another case might be a 270messaging library which calls ``Wake()`` on the receiving task once a sender has 271placed a message in a queue. 272 273.. _module-pw_async2-guides-pendables-complete: 274 275Cleaning up complete tasks 276-------------------------- 277When your pendable function has completed, make sure to return 278:cpp:type:`pw::async2::Ready` to signal that the task is complete. 279 280.. _module-pw_async2-guides-passing-data: 281 282Passing data between tasks 283========================== 284Astute readers will have noticed that the ``Wake`` method does not take any 285arguments, and ``DoPoll`` does not provide the task being polled with any 286values! 287 288Unlike callback-based interfaces, tasks (and the libraries they use) 289are responsible for storage of the inputs and outputs of events. A common 290technique is for a task implementation to provide storage for outputs of an 291event. Then, upon completion of the event, the outputs will be stored in the 292task before it is woken. The task will then be invoked again by the 293dispatcher and can then operate on the resulting values. 294 295This common pattern is implemented by the 296:cpp:class:`pw::async2::OnceSender` and 297:cpp:class:`pw::async2::OnceReceiver` types (and their ``...Ref`` counterparts). 298These interfaces allow a task to asynchronously wait for a value: 299 300.. tab-set:: 301 302 .. tab-item:: Manual ``Task`` State Machine 303 304 .. literalinclude:: examples/once_send_recv.cc 305 :language: cpp 306 :linenos: 307 :start-after: [pw_async2-examples-once-send-recv-manual] 308 :end-before: [pw_async2-examples-once-send-recv-manual] 309 310 .. tab-item:: Coroutine Function 311 312 .. literalinclude:: examples/once_send_recv.cc 313 :language: cpp 314 :linenos: 315 :start-after: [pw_async2-examples-once-send-recv-coro] 316 :end-before: [pw_async2-examples-once-send-recv-coro] 317 318More primitives (such as ``MultiSender`` and ``MultiReceiver``) are in-progress. 319Users who find that they need other async primitives are encouraged to 320contribute them upstream to ``pw::async2``! 321 322.. _module-pw_async2-guides-coroutines: 323 324Coroutines 325========== 326C++20 users can define tasks using coroutines! 327 328.. literalinclude:: examples/basic.cc 329 :language: cpp 330 :linenos: 331 :start-after: [pw_async2-examples-basic-coro] 332 :end-before: [pw_async2-examples-basic-coro] 333 334Any value with a ``Poll<T> Pend(Context&)`` method can be passed to 335``co_await``, which will return with a ``T`` when the result is ready. The 336:cpp:class:`pw::async2::PendFuncAwaitable` class can also be used to 337``co_await`` on a provided delegate function. 338 339To return from a coroutine, ``co_return <expression>`` must be used instead of 340the usual ``return <expression>`` syntax. Because of this, the 341:c:macro:`PW_TRY` and :c:macro:`PW_TRY_ASSIGN` macros are not usable within 342coroutines. :c:macro:`PW_CO_TRY` and :c:macro:`PW_CO_TRY_ASSIGN` should be 343used instead. 344 345For a more detailed explanation of Pigweed's coroutine support, see 346:cpp:class:`pw::async2::Coro`. 347 348.. _module-pw_async2-guides-timing: 349 350Timing 351====== 352When using ``pw::async2``, timing functionality should be injected 353by accepting a :cpp:class:`pw::async2::TimeProvider` (most commonly 354``TimeProvider<SystemClock>`` when using the system's built-in ``time_point`` 355and ``duration`` types). 356 357:cpp:class:`pw::async2::TimeProvider` allows for easily waiting 358for a timeout or deadline using the 359:cpp:func:`pw::async2::TimePoint::WaitFor` and 360:cpp:func:`pw::async2::TimePoint::WaitUntil` methods. 361 362Additionally, code which uses :cpp:class:`pw::async2::TimeProvider` for timing 363can be tested with simulated time using 364:cpp:class:`pw::async2::SimulatedTimeProvider`. Doing so helps avoid 365timing-dependent test flakes and helps ensure that tests are fast since they 366don't need to wait for real-world time to elapse. 367 368.. _module-pw_async2-guides-faqs: 369 370--------------------------------- 371Frequently asked questions (FAQs) 372--------------------------------- 373