• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1.. _module-pw_async:
2
3================
4pw_async
5================
6
7--------
8Overview
9--------
10Pigweed's async module provides portable APIs and utilities for writing
11asynchronous code. Currently, it provides:
12
13- Message loop APIs
14
15.. attention::
16  This module is still under construction. The API is not yet stable.
17
18----------
19Dispatcher
20----------
21Dispatcher is an API for a message loop that schedules and executes Tasks. See
22:bdg-ref-primary-line:`module-pw_async_basic` for an example implementation.
23
24Dispatcher is a pure virtual interface that is implemented by backends and
25FakeDispatcher. A virtual interface is used instead of a facade to allow
26substituting a FakeDispatcher for a Dispatcher backend in tests.
27
28Dispatcher API
29==============
30.. doxygenclass:: pw::async::Dispatcher
31   :members:
32
33
34Task API
35==============
36.. doxygenclass:: pw::async::Task
37   :members:
38
39Facade API
40==========
41
42Task
43----
44The Task type represents a work item that is submitted to a Dispatcher. The Task
45facade enables Dispatcher backends to specify custom state and methods.
46
47The active Task backend is configured with the GN variable
48``pw_async_TASK_BACKEND``. The specified target must define a class
49``pw::async::backend::NativeTask`` in the header ``pw_async_backend/task.h``
50that meets the interface requirements in ``public/pw_async/task.h``. Task will
51then trivially wrap ``NativeTask``.
52
53FakeDispatcher
54--------------
55The FakeDispatcher facade is a utility for simulating a real Dispatcher
56in tests. FakeDispatcher simulates time to allow for reliable, fast testing of
57code that uses Dispatcher. FakeDispatcher is a facade instead of a concrete
58implementation because it depends on Task state for processing tasks, which
59varies across Task backends.
60
61The active Task backend is configured with the GN variable
62``pw_async_FAKE_DISPATCHER_BACKEND``. The specified target must define a class
63``pw::async::test::backend::NativeFakeDispatcher`` in the header
64``pw_async_backend/fake_dispatcher.h`` that meets the interface requirements in
65``public/pw_async/task.h``. FakeDispatcher will then trivially wrap
66``NativeFakeDispatcher``.
67
68Testing FakeDispatcher
69^^^^^^^^^^^^^^^^^^^^^^
70The GN template ``fake_dispatcher_tests`` in ``fake_dispatcher_tests.gni``
71creates a test target that tests a FakeDispatcher backend. This enables
72one test suite to be shared across FakeDispatcher backends and ensures
73conformance.
74
75Design
76======
77
78Task Ownership
79--------------
80Tasks are owned by clients rather than the Dispatcher. This avoids either
81memory allocation or queue size limits in Dispatcher implementations. However,
82care must be taken that clients do not destroy Tasks before they have been
83executed or canceled.
84
85Getting Started
86===============
87First, configure the Task backend for the Dispatcher backend you will be using:
88
89.. code-block::
90
91   pw_async_TASK_BACKEND = "$dir_pw_async_basic:task"
92
93
94Next, create an executable target that depends on the Dispatcher backend you
95want to use:
96
97.. code-block::
98
99   pw_executable("hello_world") {
100     sources = [ "main.cc" ]
101     deps = [ "$dir_pw_async_basic:dispatcher" ]
102   }
103
104Next, instantiate the Dispatcher and post a task:
105
106.. code-block:: cpp
107
108   #include "pw_async_basic/dispatcher.h"
109
110   int main() {
111     BasicDispatcher dispatcher;
112
113     // Spawn a thread for the dispatcher to run on.
114     thread::Thread work_thread(thread::stl::Options(), dispatcher);
115
116     Task task([](pw::async::Context& ctx){
117       printf("hello world\n");
118       ctx.dispatcher->RequestStop();
119     });
120
121     // Execute `task` in 5 seconds.
122     dispatcher.PostAfter(task, 5s);
123
124     // Blocks until `task` runs.
125     work_thread.join();
126     return 0;
127   }
128
129The above example runs the dispatcher on a new thread, but it can also run on
130the current/main thread:
131
132.. code-block:: cpp
133
134   #include "pw_async_basic/dispatcher.h"
135
136   int main() {
137     BasicDispatcher dispatcher;
138
139     Task task([](pw::async::Context& ctx){
140       printf("hello world\n");
141     });
142
143     // Execute `task` in 5 seconds.
144     dispatcher.PostAfter(task, 5s);
145
146     dispatcher.Run();
147     return 0;
148   }
149
150Fake Dispatcher
151===============
152To test async code, FakeDispatcher should be dependency injected in place of
153Dispatcher. Then, time should be driven in unit tests using the ``Run*()``
154methods. For convenience, you can use the test fixture
155FakeDispatcherFixture.
156
157.. doxygenclass:: pw::async::test::FakeDispatcherFixture
158   :members:
159
160.. attention::
161
162   ``FakeDispatcher::now()`` will return the simulated time.
163   ``Dispatcher::now()`` should therefore be used to get the current time in
164   async code instead of other sources of time to ensure consistent time values
165   and reliable tests.
166
167-------
168Roadmap
169-------
170- Stabilize Task cancellation API
171- Utility for dynamically allocated Tasks
172- Bazel support
173- CMake support
174- Support for C++20 coroutines
175