Continuations in C; easy asynchronous stuff

If you have worked with asynchronous stuff, you surely must have reached a point where you need to save your current context, in order to continue the computation after you’ve received some data asynchronously.

For example, let’s say you want to upload a file to a web service, then you need to wait for some kind of id, and then you can add tags to identify it.

The simplest way to do it would be to just block until you receive the id, and then tag it. That is easy, but in the meantime nothing else can be done, and if you have a UI you’ll just see an unresponsive application.

Another approach is to create a new thread, that way other stuff can keep running while your thread waits for the id. The problem here is that threads are not exactly easy to handle, and some UI’s aren’t particularity good at handling threads. Another problem is that threads are expensive, if you have many threads being created and destroyed rapidly you are probably wasting resources.

Then if you are not lazy, you might want to save the context in a separate structure, write a separate a callback to handle the response, and the continue the processing. That is not too difficult, there are no performance penalties and should play nicely with every other code you might be running. Unfortunately it’s tedious, and if you have many contexts to store, it becomes painful.

So, here comes continuations the rescue. The concept is really simple, you reach a point in your code where don’t have anything else to compute, so you yield to allow other computations to continue, and when the relevant data is ready, the context is resumed and your code continues the execution as if nothing happened.

file.send
yield id = file.get_id # wait, but let other stuff to happen
tags.send(id)

It sound too good to be truth, and it is. Most languages don’t have support for this continuations, so you end up with ugly workarounds in order to make them work.

Fortunately there are some C primitives you can use for continuations. Wikipedia has a nice list of alternatives.

So I implemented something very basic based on Marc Lehmann’s libcoro, which makes this code possible:

struct task *test_task (const char *str) {
  struct task *t;
  void helper (struct task *t) {
    const char *tmp = str;
    printf ("%s: test: tmp=%s\n", __func__, tmp);
    task_yield (t);
    printf ("%s: after yield: tmp=%s\n", __func__, tmp);
    task_complete (t);
  }
  t = task_new (helper);
  return t;
}

The output goes like this:
main: begin
helper: test: tmp=hello world!
main: foo
helper: after yield: tmp=hello world!
main: end

I still haven’t used this for anything on real-world scenarios, but looks like an interesting idea to explore.

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s