Quickstart#

pw_async2: Cooperative async tasks for embedded

This quickstart outlines the general workflow for integrating pw_async2 into a project. It’s based on the following files in upstream Pigweed:

The example app can be built and run in upstream Pigweed with the following command:

bazelisk run //pw_async2/examples:count --config=cxx20

1. Set up build rules#

All pw_async2 projects must add a dependency on the dispatcher target. This target defines the pw::async2::Task class, an asynchronous unit of work analogous to a thread, as well as the pw::async2::Dispatcher class, an event loop used to run Task instances to completion.

Add a dependency on @pigweed//pw_async2:dispatcher in BUILD.bazel:

 1pw_cc_binary(
 2    name = "count",
 3    srcs = ["count.cc"],
 4    target_compatible_with = incompatible_with_mcu(),
 5    deps = [
 6        "//pw_allocator:libc_allocator",
 7        "//pw_async2:allocate_task",
 8        "//pw_async2:coro",
 9        "//pw_async2:coro_or_else_task",
10        "//pw_async2:dispatcher",
11        "//pw_async2:system_time_provider",
12        "//pw_chrono:system_clock",
13        "//pw_log",
14        "//pw_result",
15        "//pw_status",
16    ],
17)

Add a dependency on $dir_pw_async2:dispatcher in BUILD.gn:

 1  pw_executable("count") {
 2    deps = [
 3      "$dir_pw_allocator:libc_allocator",
 4      "$dir_pw_async2:allocate_task",
 5      "$dir_pw_async2:coro",
 6      "$dir_pw_async2:coro_or_else_task",
 7      "$dir_pw_async2:dispatcher",
 8      "$dir_pw_async2:system_time_provider",
 9      "$dir_pw_chrono:system_clock",
10      "$dir_pw_log",
11      "$dir_pw_result",
12      "$dir_pw_status",
13    ]
14    sources = [ "count.cc" ]
15  }
16

2. Inject dependencies#

Interfaces which wish to add new tasks to the event loop should accept and store a Dispatcher& reference.

1  Counter(Dispatcher& dispatcher,
2          Allocator& allocator,
3          TimeProvider<SystemClock>& time)
4      : dispatcher_(&dispatcher), allocator_(&allocator), time_(&time) {}

This allows the interface to call Dispatcher::Post in order to run asynchronous work on the dispatcher’s event loop.

3. Post one-shot work to the dispatcher#

Simple, one-time work can be queued on the dispatcher via pw::async2::EnqueueHeapFunc().

4. Post tasks to the dispatcher#

Async work that involves a series of asynchronous operations should be made into a task. This can be done by either implementing a custom task (see Implementing tasks) or by writing a C++20 coroutine (see pw::async2::Coro) and storing it in a pw::async2::CoroOrElseTask.

 1  // Posts a new asynchronous task which will count up to `times`, one count
 2  // per `period`.
 3  void StartCounting(SystemClock::duration period, int times) {
 4    CoroContext coro_cx(*allocator_);
 5    Coro<Status> coro = CountCoro(coro_cx, period, times);
 6    Task* new_task =
 7        AllocateTask<CoroOrElseTask>(*allocator_, std::move(coro), [](Status) {
 8          PW_LOG_ERROR("Counter coroutine failed to allocate.");
 9        });
10
11    // The newly allocated task will be free'd by the dispatcher
12    // upon completion.
13    dispatcher_->Post(*new_task);
14  }

The resulting task must either be stored somewhere that has a lifetime longer than the async operations (such as in a static or as a member of a long-lived class) or dynamically allocated using pw::async2::AllocateTask().

Finally, the interface instructs the dispatcher to run the task by invoking pw::async2::Dispatcher::Post().

See //pw_async2/examples/count.cc to view the complete example.

5. Build with an appropriate toolchain#

If using coroutines, remember to build your project with a toolchain that supports C++20 at minimum (the first version of C++ with coroutine support). For example, in upstream Pigweed a --config=cxx20 must be provided when building and running the example:

bazelisk build //pw_async2/examples:count --config=cxx20

Other examples#

To see another example of pw_async2 working in a minimal project, check out the following directories of Pigweed’s quickstart/bazel repo: