Code Monkey home page Code Monkey logo

async.h's Introduction

Async/await macro routines for C/C++

Description

This project contains a single header file: async.h to be included in a C/C++ project. It enables cooperative multitasking via the async/await keywords popularized by modern languages. A function declared using the async macro is a task (coroutine) that can yield before the end of the function. This can be used to wait on a long-running job (such as blocking IO), return a chunk of an incremental calculation (like a generator), or simplify a state machine into a linear progression (like a message parser). An async function can be awaited, meaning it will resume running from the previous yield point and will exit at the next yield point. There is no scheduler and no separate stacks. Tasks cannot be preemptively interrupted and local variables must be declared static to retain their values.

These macros use a couple of interesting C preprocessor and C language features, including the comma operator, varadic macros, statement expressions, and labels as values. Some of these features are exclusive to GCC, so this library will not work with other compilers.

API

/* Generates a function definition/declaration for a task name and optional
 * list of arguments. Assigns a return type of task_t* and mandatory function
 * argument task_control_t task_control.
 *
 * Examples:
 *     ASYNC(task)
 *     ASYNC(task, int arg1, int arg2)
 */
#define ASYNC(task, ...)
/* Runs a task by passing the TASK_RUN argument. Task will start execution at
 * beginning or previously yielded resume address if set. Task may yield a
 * pointer to a return result or else NULL.
 *
 * Examples:
 *     AWAIT(task, arg1, arg2)
 *     void *var = AWAIT(task, arg1, arg2)->result
 */
#define AWAIT(task, ...)
/* Resets a task by passing the TASK_RESET task_control command. Task will start
 * execution at the beginning and yield at the BEGIN macro. Task may yield a
 * pointer to a return result or else NULL.
 *
 * Examples:
 *     RESET(task, arg1, arg2)
 *     void *var = RESET(task, arg1, arg2)->result
 */
#define RESET(task, ...)
/* Drive a task to completion and return a pointer to the task_t struct.
 *
 * Examples:
 *     BLOCK(task, arg1, arg2)
 *     void *var = BLOCK(task, arg1, arg2)->result
 */
#define BLOCK(task, ...)
/* Creates and initializes the local address variable and evaluates the
 * task_control command. This macro must be included at the beginning of a task.
 * A pointer to a return result can be provided as an optional argument. This
 * result is only returned if the task is run with the TASK_RESET task_control
 * command.
 *
 * Examples:
 *     BEGIN()
 *     BEGIN(&var) where var is a static variable
 */
#define BEGIN(...)
/* Sets the address to the end of the task and returns the TASK_DONE
 * status. This macro must be included at the end of a task. A pointer to a
 * return result can be provided as an optional argument.
 *
 * Examples:
 *     END()
 *     END(&var) where var is a static variable
 */
#define END(...)
/* Exits the current task with the TASK_RUNNING status after setting the address
 * to the current line. Uses the LABEL() and CONCAT() helper macros to create a
 * unique label, then employs GCC's unary operator to get the address for that
 * label. The task will resume from this point the text time it is invoked with
 * the TASK_RUN() macro. A pointer to a return result can be provided as an
 * optional argument.
 *
 * Examples:
 *     YIELD()
 *     YIELD(&var) where var is a static variable
 */
#define YIELD(...)
/* Yield task execution until condition evaluates to true. An optional return
 * result pointer can be provided as a second argument. Note: this will yield
 * once before condition is evaluated.
 *
 * Examples:
 *     YIELD_UNTIL(bytes_available() > 0) for function int bytes_available()
 *     YIELD_UNTIL(bytes_available() > 0, &var) where var is a static variable
 */
#define YIELD_UNTIL(condition, ...)
/* Yield task execution for a duration. Yields until the number of ticks
 * elapsed is greater than duration. Requires TICK_FUNC() and TICK_TYPE to be
 * defined. An optional return result pointer can be provided as a second
 * argument.
 *
 * Examples:
 *     YIELD_FOR(100) to delay for 100 ticks
 *     YIELD_FOR(100, &var) where var is a static variable
 */
#define YIELD_FOR(duration, ...)

/* The YIELD_FOR() macro above requires a reference clock in the form of an
 * incremental counter for time comparison. Define TICK_FUNC() as the calling
 * signature for this function and TICK_TYPE as the return type. TICK_TYPE can
 * be any type that supports comparison operators. Rollovers for unsigned
 * integers are handled. Below is a sample implementation for UNIX systems.
 */
#define TICK_FUNC() time(NULL) // get seconds since January 1, 1970
#define TICK_TYPE time_t       // time_t is an integer

Examples

Examples can be found in the examples directory. Navigate to that directory and run make to build all examples.

Shows how to pass arguments to an async function. The task prints out the value of the arguments it receives each run.

Shows how to yield a value from an async function. The generator task yields an incrementing count up to max, similar to Python's range() function. The while loop in main() awaits the task until it is exhausted.

Shows the use of the BLOCK() macro to run a task to completion and retrieve a result. An async function calculates the factorial of a provided argument, in a piece-wise fashion.

Shows that tasks can be nested and an async function can be called from another async function. There are different strategies for scheduling the execution of multiple tasks and monitoring their progress.

Shows the use of the YIELD_FOR() macro to wait for a number of seconds while allowing other tasks to run.

A single-producer-single-consumer example where a producer task sets the value of a shared item and a consumer task waits for the next item. Shows the user of the YIELD_UNTIL() macro. Since this multi-tasking is single-threaded and cooperative, a mutex is not required and there is no risk of deadlock.

A stateful password parser example. Shows how to wait for a blocking IO operation (input from stdin) and parse a message piecewise without a full state machine. An OS call is made to enable non-blocking reads from stdin.


This project is inspired by the protothreads library from Contiki OS and Daniel Ozick's task macros.

async.h's People

Contributors

mogenson avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.