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
Dispatcherwithout 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();