• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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