Single header implementation of async/await concept - reformulation of my cpp-generators and async.h but for C++ this time.
Here is MOVIE of async.hpp running on board of my Arduino based test platform "Vasilii"
- It's 100% pure, portable C++.
- It's not dependent on an OS. Can be used even without OS, e.g. on Arduino.
async.hpp is all about async::task
- a structure holding state of coroutine and its bool run() method.
The task is a statefull function that can be entered and exited "in the middle". Next its run()
invocation will continue at the statment when the coroutine issued yield
(and its variants) last time.
Pseudo code
struct task {
bool flip_flop = false;
void run() {
flip_flop = true;
sleep(20);
flip_flop = false;
sleep(20);
}
bool operator()() { run(); return is_done(); }
}
task flip_flop_task;
As soon as the task will be called in a loop:
void main() {
while(true) {
flip_flop_task();
}
}
It will pulse its flip_flop state each 40 ms.
Function | Description |
---|---|
TASK(name) {...} | Declaration of the task - async::task derived (CRTP) structure |
TASK_BEGIN | Mark beginning of code section of the coroutine (its run() method) |
TASK_END | Mark end of ode section of the coroutine. |
TASK_STOP | Stop execution of the task, a.k.a. async "return". |
YIELD | Yield execution until it's invoked again |
AWAIT(cond) | Block execution until cond is true |
START_TASK(name,params...) | Initialize task state and start its execution |
RUN_TASK(name,params...) | Run task and wait until it ends |
Auxiliary primitives
Function | Description |
---|---|
async::timer | waitable timer |
async::semaphore | semaphore/event |
Ported example of async.h:
#include "async.hpp"
TASK(example) {
async::timer timer; // state variable
TASK_BEGIN
while(true) {
if(initiate_io()) {
timer.start(200);
AWAIT(io_completed() || timer.is_expired());
read_data();
}
}
TASK_END
}
You can also execute nested async tasks. Here is an example of my Arduino robot implementation :
TASK(rollback_task) {
int angle_to_turn; // state variable
public:
void init(int angle = 15) { angle_to_turn = angle; }
TASK_BEGIN
engine.set(-0.4); // move backwards in slow speed
SLEEP(1200); // for 1.2 seconds
// run task of turning the device to the given angle:
RUN_TASK(turn_to_task, random(2)? -angle_to_turn: angle_to_turn);
engine.stop(); // stop the engine
TASK_END
} rollback_task;
The whole robot's code is just a composition of such tasks, some of them run in parallel, some one by one. There is no OS, no threads, on the device just a loop()
routine called by the processor. The async.hpp allows to implement parallelism and asynchronous execution in 8kb of RAM the device has on board.
- You cannot use switch statements within an async subroutine - execution state management is a switch itself.
- You cannot make blocking system calls. Either use poll() or completion I/O primitives.
There is premake5 file there. So you can generate VS, XCode, etc. projects:
- Download premake5
- run
premake5.exe vs2015
in the /code folder to generate VS2015 project and workspace.
I am using cpp-generators and the async::task (combined with libuv for I/O) intensively in my Sciter Engine. You can find Sciter SDK on GitHub too. This allows me to keep Sciter small, fast and manageable.
As of the robot pet project, read my Refreshing AI basics … article.