blob: 3ae6983a25e0750565a1f57df30f8121f6200547 [file]
.. _module-pw_async2-coro:
==========
Coroutines
==========
.. pigweed-module-subpage::
:name: pw_async2
For projects using C++20, ``pw_async2`` provides first-class support for
coroutines via :cc:`Coro <pw::async2::Coro>`. This allows writing asynchronous
logic in a sequential, synchronous style, eliminating the need to write explicit
state machines. The ``co_await`` keyword is used to suspend execution until an
asynchronous operation is ``Ready``.
See also :ref:`docs-blog-05-coroutines`, a blog post on how Pigweed implements
coroutines without heap allocation, and challenges encountered along the way.
.. _module-pw_async2-coro-tasks:
----------------
Using coroutines
----------------
Define a coroutine
==================
A ``pw_async2`` coroutine is a function that with :cc:`CoroContext
<pw::async2::CoroContext>` as its first parameter and a return type of
:cc:`Coro<T> <pw::async2::Coro>`. Here is an example of a coroutine that returns
:cc:`pw::Status`:
.. literalinclude:: examples/basic_coro.cc
:language: cpp
:linenos:
:start-after: [pw_async2-examples-basic-coro]
:end-before: [pw_async2-examples-basic-coro]
Any :ref:`future <module-pw_async2-futures>` or coroutine can be passed to
``co_await``, which evaluates to a ``value_type`` when the result is ready. To
return from a coroutine, use ``co_return <expression>`` instead of the usual
``return <expression>`` syntax.
.. tip::
Use :cc:`PW_CO_TRY` and :cc:`PW_CO_TRY_ASSIGN` instead of :cc:`PW_TRY` and
:cc:`PW_TRY_ASSIGN` when working with :cc:`pw::Status` or :cc:`pw::Result` in
a coroutine. These macros use ``co_return`` instead of ``return``.
Run a coroutine
===============
Run a coroutine as a ``pw_async2`` :cc:`task <pw::async2::Task>` using
:cc:`Dispatcher::Post`. The following posts a coroutine as a :cc:`CoroTask
<pw::async2::CoroTask>`:
.. literalinclude:: examples/basic_coro.cc
:language: cpp
:start-after: [pw_async2-examples-basic-allocated]
:end-before: [pw_async2-examples-basic-allocated]
The coroutine can also be instantiated directly and passed to ``Post``, though
this requires listing the allocator twice:
.. literalinclude:: examples/basic_coro.cc
:language: cpp
:start-after: [pw_async2-examples-basic-allocated-explicit]
:end-before: [pw_async2-examples-basic-allocated-explicit]
The previous examples use :cc:`CoroTask <pw::async2::CoroTask>`, which crashes
if coroutione stack allocation fails. To handle allocation failures gracefully
with :cc:`FallibleCoroTask <pw::async2::FallibleCoroTask>`, pass an allocation
error handler function after the coroutine:
.. literalinclude:: examples/basic_coro.cc
:language: cpp
:start-after: [pw_async2-examples-basic-allocated-fallible]
:end-before: [pw_async2-examples-basic-allocated-fallible]
:cc:`CoroTask <pw::async2::CoroTask>` or :cc:`FallibleCoroTask
<pw::async2::FallibleCoroTask>` can be stack or statically allocated instead of
dynamically allocated with :cc:`Dispatcher::Post`. This is not recommended, as
it is more complex and does not eliminate all allocations. Coroutines always
dynamically allocate their stacks.
.. literalinclude:: examples/basic_coro.cc
:language: cpp
:start-after: [pw_async2-examples-basic-coro-task]
:end-before: [pw_async2-examples-basic-coro-task]
For a more details about Pigweed's coroutine support, see :cc:`Coro
<pw::async2::Coro>`.
------
Memory
------
When using C++20 coroutines, the compiler generates code to save the
coroutine's state (including local variables) across suspension points
(``co_await``). ``pw_async2`` hooks into this mechanism to control where this
state is stored and support gracefully handling allocation failures.
A ``pw_async2`` coroutine must accept a :cc:`CoroContext
<pw::async2::CoroContext>` by value as its first argument. :cc:`CoroContext
<pw::async2::CoroContext>` wraps a reference to a :cc:`pw::Allocator`, and this
allocator is used to allocate the coroutine frame. When instantiating a
coroutine, simply pass an allocator as the first argument; ``CoroContext`` is
implicitly constructible from an ``Allocator&``.
If allocation fails, the resulting ``Coro`` object is invalid. Coroutine
execution halts, and what happens next depends on the task executing the
coroutine. :cc:`CoroTask <pw::async2::CoroTask>` crashes with ``PW_CRASH`` on
allocation failure. :cc:`FallibleCoroTask <pw::async2::FallibleCoroTask>`
invokes an error handler function instead.
.. _module-pw_async2-coro-passing-data:
-------------------------------
Passing data between coroutines
-------------------------------
Coroutines run within ``pw_async2`` tasks and can pass data in all the same
ways. See :ref:`module-pw_async2-channels` for details about passing data with
channels.