[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(&timer); await(io_completed() || timer_expired(&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.
Interesting. I’ve used boost’s async before but this looks really nice and easy / lightweight.
May want to fix up the formatting of the greater and less than symbols as they appear i nthe post.
I was kind of wondering what kind of wonky C-derivative they where using before it dawned on me that the formatting was fubar. Isn’t automatic XML-entity conversion great…
Yeah, and this header does similar automagic conversion for your code, but don’t try anything fancy like two async statements in one line or switch – you will feel the wrath of a compiler. I’ve recently done similar trick without syntactic sugar, just a function with switch statement and it allows you to better see what’s going on.
I fixed them more than once. As far as I can tell, every time someone edits they revert. They MIGHT be fixed now ;-)
Adam rocks!
Using this in C++ will mess you up bigtime. As it “re-runs” the function each time, so stack objects get destroyed and created again. It can also jump passed initialization of stack variables.
While a cool trick, it has pretty hard limits.
C++ isn’t special in that regard. If you want your function to not setup stack variables just don’t setup stack variables. Your async operation object can hold that for you.
Which can be solved via simply adding static keyword before your variables/objects, as you usually do not dispatch several instances of your asycnhronous routines, or protothreads.
You may, if you wish, pass them through pointer/reference, if you need several instances of the same protothread.
The magic here is to separate your tasks into easily scheduable tasks, instead of building hard to maintain main loops and state machines. This is basically a state machine abstraction.
Just consider a case where you need to serve several TCP connections without preemptive scheduling. You could allocate a structure that contains the information about the TCP connection in the interrupt handler, create a new protothread, pass the struct to that protothread through a pointer, let protothread do its serving, and in your main loop just loop over these protothreads, and go idle/sleep if you have none to call anymore.
I need autoconf
This sounds like something you’d rather do with a programming language that has a garbage collector.
That’s pretty much the universal answer to any question involving C.
A very interesting idea. The only thing to remember is that local variables won’t preserve its value between async executions.
Or, you can use the standard way with setjmp/longjmp so it saves your callstack & stack (instead of recreating it each time) with no preprocessing hack whatsoever. You don’t have to leave your function, you can use switch etc…
See https://www.embeddedrelated.com/showarticle/455.php
Regarding the ‘async routine’ idea in general .. Granted there may be a use for this, but it seems like it would have to be weird corner cases. There’s a use for virtually anything if you look hard enough, but my concern is that this doesn’t seem like something that should be needed all that often. All too often I’ve seen inexperienced implementors start forking threads all over the place to handle sequential jobs, and this looks like more of that sort of shiny object that lures the neophytes. I’m not saying there’s no place for this, you just probably don’t want to construct your application entirely from it. But given how often apps on my phone get deadlocked and have to be killed and restarted, I wonder. Thought they were doing it with excessive multithreading and locks, but abusing this stuff would do the trick nicely.
If you do microcontroller programming, there are tons of cases where this is useful, mainly when you work with interrupts. If you do a simple event queue, and manage everything with events, these async functions are really, really useful.
Or when you need to use more than one peripherial at a time, like one device on usart, one on spi and you have dma transfers going on.
Ok yeah I broke the rule, posted first googled 2nd. Looks like there would be more places than I thought, that this would help. Still not sure about dressing up what essentially is lightweight scheduling logic to look like something less serious. Don’t get me wrong I think pointless code ceremonies are also suboptimal, but simplifying the syntax to where the unsuspecting can appropriate it seems to invite trouble.
I don’t get it. The description of await sounds like repeatedly calling a function like
̧“`
if( no_data_yet() ) return;
process_data();
“`
What am I missing?
This is just syntactic sugar for what you describe.
Technically speaking this whole mess can be used to create a function that does some piece of work than returns control to the place from which it was called and when it’s called again it resumes from the part when it returned, ans doesn’t do that piece of work again. In practice the easier way to do is to set up a global and volatile flag and go this way:
if (flag == 0){
do something;
flag == 1;
}
if (flag == 1 and we_have_data()) flag = 2; else return;
if (flag == 2){
do something else;
flag = 3;
}
and repeat this for all asynchronous conditions. Also the point of execution for this function can be changed by other function One can also use switch-case statement at the beginning of the function and some goto statements to move around it. Yes, C has goto, not many remember that, and even less use it.
However in embedded world I just use ISRs to do small tasks dependent on timing, state of peripherals or on external stimuli, and use FSM for everything else. If I need to wait for hardware or software flag, then I just use while (f!lag); to freeze program in infiniloop. ISR will unfreeze it by changing the flag to 1. Or hardware would do it if flag is part of some register.
I also tend to avoid any syntactic sugar because it causes cancer of the semicolon. I don’t even use pointers unless there is no other way…
new illustrator for the articles? something about hackaday feels different
this reminds me a lot of Simon Tatham’s coroutines trick from a while back, ca 2000:
https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
async/await always struck me as a syntactic sugar for runtimes that are inherently asynchronous (e.g. nodejs), and useful in such environments because you often don’t have the simplicity of assuming sequential execution. Since C runtimes are typically synchronous, it’s not clear to me how useful this is unless you’re coming from a nodejs mindset, but maybe there’s more to it than has met my eye.
I work on highly multi-tasked networking code where this idea would be extremely useful, for example to see if the server has replied to a request (or one/some of multiple requests) or not.
However my OCD doesn’t like that it silently returns from the middle of the function. My experience is that “in at the top, out at the bottom” makes code easier to read and makes it less likely to introduce bugs (specifically bugs that are hard to trace, e.g. where an output parameter remains uninitialized).
Also the fact that it silently(?) breaks if someone introduces a nested switch-case, makes it unattractive.
I do like the idea of using line numbers as case parameters. Perhaps a rewrite using goto combined with the __LINE__ macro would solve the “no switch-case allowed” problem.
This has been around for ages. Look at protothreads by Adam Dunkels.
http://dunkels.com/adam/pt/
Was about to echo the same, ContikiOS has protothreads, and If memory serves they had co-routines too, but dropped support eventually as protothreads were considered better?
I put together something similar a couple of years ago to make it easier to program microcontrollers. My goal was to support a kind of composability, so that timing-sensitive code (like reading sensors or generating signals) can be combined without having to resort to hand-coded scheduling. I also managed to wrangle C++ lambdas/closures to support local variables that hold their value across the “yield” operations.
https://github.com/samguyer/adel
That is pretty neat! I might have to try it in one of my projects down the road.
There’s a simple library in the uIP stack that does this called PTHREADS.
https://github.com/adamdunkels/uip/
https://github.com/adamdunkels/uip/blob/master/uip/pt.h
The linked article explicitly says
followed by a link to protothreads…
This is ill-advised. Issues will arise when you have a significantly-large codebase and attempt to debug a complex bug: this syntatic sugar will only stymie those efforts. In fact it may well introduce novel bugs, too.
Moreover, I honestly don’t understand the motivation: if you’re using C, write idiomatic C! This is *not* idiomatic C, and anyone is used to idiomatic C is going to run away from any codebase using this sugar as quickly as they can. This is akin to advising a non-native English speaker that it’s OK to speak a heavily-inflected version of English: it might be OK for the group of people that all speak that way, but the vast majority of the world’s English speakers will have trouble understanding.
Please, don’t use this!
I have been doing this for years.
In my Arduino Template Library I have called it a Task. A class that manages one async task.
https://github.com/obiwanjacobi/atl/blob/master/Source/Code/ArduinoTemplateLibrary/Task.h
My latest incarnation is similar to this also called async -but no await, because I felt the confused usage.
https://github.com/obiwanjacobi/Zingularity/blob/master/Source/Jacobi.CpuZ80/Async.h
Try it, it’s wonderful.
Doing this in C makes the similarities of current crop of asynchronous language enhancements with the cooperative multi-tasking environments of the early ’80s that much more striking.
Everything old is new again.
s_task has await/async pair used as what’s in c# or javascript.
https://github.com/xhawk18/s_task
void sub_task(__async__, void* arg) {
int i;
for (i = 0; i < 5; ++i) {
printf("i = %d\n", i);
s_task_sleep(__await__, 1);
}
}