blob: 46657c5dc3b53252902795676b1177c5d8ea5162 [file] [log] [blame]
.. _module-pw_async2:
=============
pw_async2
=============
.. pigweed-module::
:name: pw_async2
- **Simple Ownership**: Say goodbye to that jumble of callbacks and shared
state! Complex tasks with many concurrent elements can be expressed by
simply combining smaller tasks.
- **Efficient**: No dynamic memory allocation required.
- **Pluggable**: Your existing event loop, work queue, or task scheduler
can run the ``Dispatcher`` without any extra threads.
- **Coroutine-capable**: C++20 coroutines and Rust ``async fn`` work just
like other tasks, and can easily plug into an existing ``pw_async2``
systems.
:cpp:class:`pw::async2::Task` is Pigweed's async primitive. ``Task`` objects
are cooperatively-scheduled "threads" which yield to the
:cpp:class:`pw::async2::Dispatcher` when waiting. When the ``Task`` is able to make
progress, the ``Dispatcher`` will run it again. For example:
.. code-block:: cpp
#include "pw_async2/dispatcher.h"
#include "pw_async2/poll.h"
#include "pw_result/result.h"
using ::pw::async2::Context;
using ::pw::async2::Poll;
using ::pw::async2::Ready;
using ::pw::async2::Pending;
using ::pw::async2::Task;
class ReceiveAndSend: public Task {
public:
ReceiveAndSend(Receiver receiver, Sender sender):
receiver_(receiver), sender_(sender) {}
Poll<> Pend(Context& cx) {
if (!send_future_) {
// ``PendReceive`` checks for available data or errors.
//
// If no data is available, it will grab a ``Waker`` from
// ``cx.Waker()`` and return ``Pending``. When data arrives,
// it will call ``waker.Wake()`` which tells the ``Dispatcher`` to
// ``Pend`` this ``Task`` again.
Poll<pw::Result<Data>> new_data = receiver_.PendReceive(cx);
if (new_data.is_pending()) {
// The ``Task`` is still waiting on data. Return ``Pending``,
// yielding to the dispatcher. ``Pend`` will be called again when
// data becomes available.
return Pending();
}
if (!new_data->ok()) {
PW_LOG_ERROR("Receiving failed: %s", data->status().str());
// The ``Task`` completed;
return Ready();
}
Data& data = **new_data;
send_future_ = sender_.Send(std::move(data));
}
// ``PendSend`` attempts to send ``data_``, returning ``Pending`` if
// ``sender_`` was not yet able to accept ``data_``.
Poll<pw::Status> sent = send_future_.Pend(cx);
if (sent.is_pending()) {
return Pending();
}
if (!sent->ok()) {
PW_LOG_ERROR("Sending failed: %s", sent->str());
}
return Ready();
}
private:
Receiver receiver_;
Sender sender_;
// ``SendFuture`` is some type returned by `Sender::Send` that offers a
// ``Pend`` method similar to the one on ``Task``.
std::optional<SendFuture> send_future_ = std::nullopt;
};
Tasks can then be run on a :cpp:class:`pw::async2::Dispatcher` using the
:cpp:func:`pw::async2::Dispatcher::Post` method:
.. code-block:: cpp
#include "pw_async2/dispatcher.h"
int main() {
ReceiveAndSendTask task(SomeMakeReceiverFn(), SomeMakeSenderFn());
Dispatcher dispatcher;
dispatcher.Post(task);
dispatcher.RunUntilComplete(task);
return 0;
}
.. _module-pw_async2-coroutines:
----------
Coroutines
----------
C++20 users can also define tasks using coroutines!
.. literalinclude:: examples/coro.cc
:language: cpp
:linenos:
:start-after: [pw_async2-examples-coro-injection]
:end-before: [pw_async2-examples-coro-injection]
Any value with a ``Poll<T> Pend(Context&)`` method can be passed to
``co_await``, which will return with a ``T`` when the result is ready.
To return from a coroutine, ``co_return <expression>`` must be used instead of
the usual ``return <expression>`` syntax. Because of this, the
:c:macro:`PW_TRY` and :c:macro:`PW_TRY_ASSIGN` macros are not usable within
coroutines. :c:macro:`PW_CO_TRY` and :c:macro:`PW_CO_TRY_ASSIGN` should be
used instead.
For a more detailed explanation of Pigweed's coroutine support, see the
documentation on the :cpp:class:`pw::async2::Coro<T>` type.
-----------------
C++ API reference
-----------------
.. doxygenclass:: pw::async2::Task
:members:
.. doxygenclass:: pw::async2::Poll
:members:
.. doxygenfunction:: pw::async2::Ready()
.. doxygenfunction:: pw::async2::Ready(std::in_place_t, Args&&... args)
.. doxygenfunction:: pw::async2::Ready(T&& value)
.. doxygenfunction:: pw::async2::Pending()
.. doxygenclass:: pw::async2::Context
:members:
.. doxygenclass:: pw::async2::Waker
:members:
.. doxygenclass:: pw::async2::Dispatcher
:members:
.. doxygenclass:: pw::async2::Coro
:members:
.. doxygenclass:: pw::async2::CoroContext
:members:
-------------
C++ Utilities
-------------
.. doxygenfunction:: pw::async2::AllocateTask(pw::allocator::Allocator& allocator, Pendable&& pendable)
.. doxygenfunction:: pw::async2::AllocateTask(pw::allocator::Allocator& allocator, Args&&... args)
.. doxygenclass:: pw::async2::PendFuncTask
:members:
.. doxygenclass:: pw::async2::PendableAsTask
:members:
.. toctree::
:hidden:
:maxdepth: 1
Backends <backends>