Asynchronous Routines for C

[Sandro Magi] noted that the async/await idiom has become more prevalent in programming recently. According to him, he first encountered it in C# but has found examples of it in JavaScript and Rust, too. The idea is simple: allow a function to return but come back later to complete something that takes a long time. Of course, multithreading is one answer to this, but generally, this technique implies more of a coroutine setup where functions cooperate to some degree to get good behavior. [Sandro] took some ideas from the existing protothread library and used it to create a system to get this effect in C and, by extension, C++.

Adding this “library” is as simple as including a header file. All the magic occurs at the preprocessor and compiler. There’s no code to link. The async routines only need two bytes of overhead and — unlike proper threads — don’t need a preallocated private stack.

Here’s a simple example from the project’s GitHub:


#include "async.h"

typedef struct {
async_state; // declare the asynchronous state
timer timer; // declare local state
} example_state;
example_state pt;

async example(example_state *pt) {
async_begin(pt);

while(1) {
if(initiate_io()) {
timer_start(&pt->timer);
await(io_completed() || timer_expired(&pt->timer));
read_data();
}
}
async_end;
}

Of course, initiate_io, timer_start, timer_expired, read_data, and io_completed are  defined somewhere else. The idea is simple. The first time you call the async function, it will run like normal until it hits some form of await. Then it may return. Each time after that, the function jumps back to the await statement. You can, of course, have multiple await statements.

In the example,s case, once I/O starts, the code will keep returning until either the I/O completes or the timer expires. Then it will return a final answer.

The magic here is due to a switch statement (and one limitation: you can’t use a switch statement inside the async code). The function returns the line number it wants to jump to next time or -1 to end the cycle. Each await generates a case label that has the current line number. This is easy to see if you pass -E to the gcc compiler and look at the output after preprocessing:


async example(struct async *pt) {
switch((pt)->_async_kcont) { case 0:;

while(1) {
if(initiate_io()) {
timer_start(&xtimer);
case 21: if (!(io_completed() || timer_expired(&xtimer))) return 21;
read_data();
}
}
default: return ASYNC_DONE; };

Sure, you could roll it yourself, but this is a handy syntactic shortcut. We’ve seen the use of protothreads on small systems before. Protothreads even earned one hacker a job.



from Hackaday https://ift.tt/2l6azX5
via IFTTT