1.. _module-pw_async2-quickstart-guides: 2 3=================== 4Quickstart & guides 5=================== 6.. pigweed-module-subpage:: 7 :name: pw_async2 8 9.. _module-pw_async2-guides: 10 11------ 12Guides 13------ 14 15Implementing tasks 16================== 17:cpp:class:`pw::async2::Task` instances complete one or more asynchronous 18operations. They are the top-level "thread" primitives of ``pw_async2``. 19 20You can use one of the concrete subclasses of ``Task`` that Pigweed provides: 21 22* :cpp:class:`pw::async2::CoroOrElseTask`: Delegates to a provided 23 coroutine and executes an ``or_else`` handler function on failure. 24* :cpp:class:`pw::async2::PendFuncTask`: Delegates to a provided 25 function. 26* :cpp:class:`pw::async2::PendableAsTask`: Delegates to a type 27 with a :cpp:func:`pw::async2::Pend` method. 28* :cpp:func:`pw::async2::AllocateTask`: Creates a concrete subclass of 29 ``Task``, just like ``PendableAsTask``, but the created task is 30 dynamically allocated and frees the associated memory upon 31 completion. 32 33Or you can subclass ``Task`` yourself. See :cpp:class:`pw::async2::Task` 34for more guidance on subclassing. 35 36.. _module-pw_async2-guides-tasks: 37 38How a dispatcher manages tasks 39============================== 40The purpose of a :cpp:class:`pw::async2::Dispatcher` is to keep track of a set 41of :cpp:class:`pw::async2::Task` objects and run them to completion. The 42dispatcher is essentially a scheduler for cooperatively-scheduled 43(non-preemptive) threads (tasks). 44 45While a dispatcher is running, it waits for one or more tasks to waken and then 46advances each task by invoking its :cpp:func:`pw::async2::Task::DoPend` method. 47The ``DoPend`` method is typically implemented manually by users, though it is 48automatically provided by coroutines. 49 50If the task is able to complete, ``DoPend`` will return ``Ready``, in which case 51the task is then deregistered from the dispatcher. 52 53If the task is unable to complete, ``DoPend`` must return ``Pending`` and arrange 54for the task to be woken up when it is able to make progress again. Once the 55task is rewoken, the task is re-added to the ``Dispatcher`` queue. The 56dispatcher will then invoke ``DoPend`` once more, continuing the cycle until 57``DoPend`` returns ``Ready`` and the task is completed. 58 59The following sequence diagram summarizes the basic workflow: 60 61.. mermaid:: 62 63 sequenceDiagram 64 participant e as External Event e.g. Interrupt 65 participant d as Dispatcher 66 participant t as Task 67 e->>t: Init Task 68 e->>d: Register task via Dispatcher::Post(Task) 69 d->>d: Add task to queue 70 d->>t: Run task via Task::DoPend() 71 t->>t: Task is waiting for data and can't yet complete 72 t->>e: Arrange for rewake via PW_ASYNC_STORE_WAKER 73 t->>d: Indicate that task is not complete via Pending() 74 d->>d: Remove task from queue 75 d->>d: Go to sleep because task queue is empty 76 e->>e: The data that the task needs has arrived 77 e->>d: Rewake via Waker::Wake() 78 d->>d: Re-add task to queue 79 d->>t: Run task via Task::DoPend() 80 t->>t: Task runs to completion 81 t->>d: Indicate that task is complete via Ready() 82 d->>d: Deregister the task 83 84.. _module-pw_async2-guides-pendables: 85 86Implementing invariants for pendable functions 87============================================== 88.. _invariants: https://stackoverflow.com/a/112088 89 90Any ``Pend``-like function or method similar to 91:cpp:func:`pw::async2::Task::DoPend` that can pause when it's not able 92to make progress on its task is known as a **pendable function**. When 93implementing a pendable function, make sure that you always uphold the 94following `invariants`_: 95 96* :ref:`module-pw_async2-guides-pendables-incomplete` 97* :ref:`module-pw_async2-guides-pendables-complete` 98 99.. note:: Exactly which APIs are considered pendable? 100 101 If it has the signature ``(Context&, ...) -> Poll<T>``, 102 then it's a pendable function. 103 104.. _module-pw_async2-guides-pendables-incomplete: 105 106Arranging future completion of incomplete tasks 107----------------------------------------------- 108When your pendable function can't yet complete: 109 110#. Do one of the following to make sure the task rewakes when it's ready to 111 make more progress: 112 113 * Delegate waking to a subtask. Arrange for that subtask's 114 pendable function to wake this task when appropriate. 115 116 * Arrange an external wakeup. Use :c:macro:`PW_ASYNC_STORE_WAKER` 117 to store the task's waker somewhere, and then call 118 :cpp:func:`pw::async2::Waker::Wake` from an interrupt or another thread 119 once the event that the task is waiting for has completed. 120 121 * Re-enqueue the task with :cpp:func:`pw::async2::Context::ReEnqueue`. 122 This is a rare case. Usually, you should just create an immediately 123 invoked ``Waker``. 124 125#. Make sure to return :cpp:type:`pw::async2::Pending` to signal that the task 126 is incomplete. 127 128In other words, whenever your pendable function returns 129:cpp:type:`pw::async2::Pending`, you must guarantee that 130:cpp:func:`pw::async2::Context::Wake` is called once in the future. 131 132For example, one implementation of a delayed task might arrange for its ``Waker`` 133to be woken by a timer once some time has passed. Another case might be a 134messaging library which calls ``Wake()`` on the receiving task once a sender has 135placed a message in a queue. 136 137.. _module-pw_async2-guides-pendables-complete: 138 139Cleaning up complete tasks 140-------------------------- 141When your pendable function has completed, make sure to return 142:cpp:type:`pw::async2::Ready` to signal that the task is complete. 143 144.. _module-pw_async2-guides-passing-data: 145 146Passing data between tasks 147========================== 148Astute readers will have noticed that the ``Wake`` method does not take any 149arguments, and ``DoPoll`` does not provide the task being polled with any 150values! 151 152Unlike callback-based interfaces, tasks (and the libraries they use) 153are responsible for storage of the inputs and outputs of events. A common 154technique is for a task implementation to provide storage for outputs of an 155event. Then, upon completion of the event, the outputs will be stored in the 156task before it is woken. The task will then be invoked again by the 157dispatcher and can then operate on the resulting values. 158 159This common pattern is implemented by the 160:cpp:class:`pw::async2::OnceSender` and 161:cpp:class:`pw::async2::OnceReceiver` types (and their ``...Ref`` counterparts). 162These interfaces allow a task to asynchronously wait for a value: 163 164.. tab-set:: 165 166 .. tab-item:: Manual ``Task`` State Machine 167 168 .. literalinclude:: examples/once_send_recv.cc 169 :language: cpp 170 :linenos: 171 :start-after: [pw_async2-examples-once-send-recv-manual] 172 :end-before: [pw_async2-examples-once-send-recv-manual] 173 174 .. tab-item:: Coroutine Function 175 176 .. literalinclude:: examples/once_send_recv.cc 177 :language: cpp 178 :linenos: 179 :start-after: [pw_async2-examples-once-send-recv-coro] 180 :end-before: [pw_async2-examples-once-send-recv-coro] 181 182More primitives (such as ``MultiSender`` and ``MultiReceiver``) are in-progress. 183Users who find that they need other async primitives are encouraged to 184contribute them upstream to ``pw::async2``! 185 186.. _module-pw_async2-guides-coroutines: 187 188Coroutines 189========== 190C++20 users can define tasks using coroutines! 191 192.. literalinclude:: examples/basic.cc 193 :language: cpp 194 :linenos: 195 :start-after: [pw_async2-examples-basic-coro] 196 :end-before: [pw_async2-examples-basic-coro] 197 198Any value with a ``Poll<T> Pend(Context&)`` method can be passed to 199``co_await``, which will return with a ``T`` when the result is ready. 200 201To return from a coroutine, ``co_return <expression>`` must be used instead of 202the usual ``return <expression>`` syntax. Because of this, the 203:c:macro:`PW_TRY` and :c:macro:`PW_TRY_ASSIGN` macros are not usable within 204coroutines. :c:macro:`PW_CO_TRY` and :c:macro:`PW_CO_TRY_ASSIGN` should be 205used instead. 206 207For a more detailed explanation of Pigweed's coroutine support, see 208:cpp:class:`pw::async2::Coro`. 209 210.. _module-pw_async2-guides-timing: 211 212Timing 213====== 214When using ``pw::async2``, timing functionality should be injected 215by accepting a :cpp:class:`pw::async2::TimeProvider` (most commonly 216``TimeProvider<SystemClock>`` when using the system's built-in ``time_point`` 217and ``duration`` types). 218 219:cpp:class:`pw::async2::TimeProvider` allows for easily waiting 220for a timeout or deadline using the 221:cpp:func:`pw::async2::TimePoint::WaitFor` and 222:cpp:func:`pw::async2::TimePoint::WaitUntil` methods. 223 224Additionally, code which uses :cpp:class:`pw::async2::TimeProvider` for timing 225can be tested with simulated time using 226:cpp:class:`pw::async2::SimulatedTimeProvider`. Doing so helps avoid 227timing-dependent test flakes and helps ensure that tests are fast since they 228don't need to wait for real-world time to elapse. 229 230.. _module-pw_async2-guides-faqs: 231 232--------------------------------- 233Frequently asked questions (FAQs) 234--------------------------------- 235