pw_async2#

Cooperative async tasks for embedded

Unstable C++

pw_async2 is a cooperatively scheduled asynchronous framework for C++, optimized for use in resource-constrained systems. It helps you write complex, concurrent applications without the overhead of traditional preemptive multithreading. The design prioritizes efficiency, minimal resource usage (especially memory), and testability.

Benefits#

  • 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.

Example#

Informed poll is the core design philosophy behind pw_async2. Task is the main async primitive. It’s a cooperatively scheduled “thread” which yields to the Dispatcher when waiting. When a Task is able to make progress, the Dispatcher runs it again:

 1#include "pw_async2/dispatcher_for_test.h"
 2#include "pw_async2/poll.h"
 3#include "pw_async2/try.h"
 4#include "pw_log/log.h"
 5#include "pw_result/result.h"
 6
 7namespace {
 8
 9using ::pw::async2::Context;
10using ::pw::async2::Poll;
11using ::pw::async2::Ready;
12using ::pw::async2::Task;
13
14class MyReceiver;
15class MySender;
16
17// Receive then send that data asynchronously. If the reader or writer aren't
18// ready, the task suspends when TRY_READY invokes ``return Pending()``.
19class ReceiveAndSend final : public Task {
20 public:
21  ReceiveAndSend(MyReceiver receiver, MySender sender)
22      : receiver_(receiver), sender_(sender), state_(kReceiving) {}
23
24  Poll<> DoPend(Context& cx) final {
25    switch (state_) {
26      case kReceiving: {
27        PW_TRY_READY_ASSIGN(auto new_data, receiver_.PendReceive(cx));
28        if (!new_data.ok()) {
29          PW_LOG_ERROR("Receiving failed: %s", new_data.status().str());
30          return Ready();  // Completes the task.
31        }
32        // Start transmitting and switch to transmitting state.
33        send_future_ = sender_.Send(std::move(*new_data));
34        state_ = kTransmitting;
35      }
36        [[fallthrough]];
37      case kTransmitting: {
38        PW_TRY_READY_ASSIGN(auto sent, send_future_->Pend(cx));
39        if (!sent.ok()) {
40          PW_LOG_ERROR("Sending failed: %s", sent.str());
41        }
42        return Ready();  // Completes the task.
43      }
44    }
45  }
46
47 private:
48  MyReceiver receiver_;  // Can receive data async.
49  MySender sender_;      // Can send data async.
50  std::optional<SendFuture> send_future_ = std::nullopt;
51
52  enum State { kTransmitting, kReceiving };
53  State state_;
54};
55
56}  // namespace

Tasks can then be run on a Dispatcher using the Post() method:

1  auto task = ReceiveAndSend(std::move(receiver), std::move(sender));
2  DispatcherForTest dispatcher;
3  // Registers `task` to run on the dispatcher.
4  dispatcher.Post(task);
5  // Sets the dispatcher to run until all `Post`ed tasks have completed.
6  dispatcher.RunToCompletion();

Learn more#

Informed poll

The core design philosophy behind pw_async2. We strongly encourage all pw_async2 users to internalize this concept before attempting to use pw_async2!

Codelab

Get hands-on experience with the core concepts of pw_async2 by building a simple, simulated vending machine.

Quickstart

How to quickly integrate pw_async2 into your project and start using basic features.

Guides

How to use dispatchers to coordinate tasks, pass data between tasks, and more.

Reference

C/C++ API reference for Task, Dispatcher, Coro, and more.

Code size analysis

Reports on the code size cost of adding pw_async2 to a system.

Dispatchers

You can use a Pigweed-provided dispatcher or roll your own.

Futures

Futures are the basic async primitive in pw_async2. Learn about future ownership, lifetimes, polling, composability, and more.

Channels

Channels are the primary mechanism for inter-task communication in pw_async2. Learn about channel creation, handles, sending and receiving, lifetimes, allocation, and more.

Coroutines

How to define tasks with coroutines, allocate memory, perform async operations from coroutines, and more.