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: