Faking coroutines in C# – Part 1

I happen to love coroutines. When making games, you sometimes want to start a task that takes some time to complete. Let’s consider a simple task: when a button is pressed, wait for 1 second, play a sound, wait for 1 more second and then play another sound.

So let’s consider you have a task manager, something like this:

1
2
3
4
5
6
7
List<Func<bool>> tasks;
//...
foreach (var task in tasks)
{
    if (!task())
        tasks.Remove(task); // I know this doesn't work. Please bear with me here
}

So let’s implement our task. State machines are very useful for doing this kind of stuff. Say something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
tasks.Add(delegate bool() // I happen to prefer this syntax to the lambda one
{
    if (buttonPressed) // Whatever that does
    {
        int framesLeft = 0;
        int state = 0;
        tasks.Add(delegate bool()
        {
            switch (state)
            {
                case 0:
                    framesLeft = 60;
                    state = 1;
                    break;
                case 1:
                    framesLeft--;
                    if (framesLeft <= 0)
                    {
                        PlaySound(soundA);
                        framesLeft = 60;
                        state = 1;
                    }
                    break;
                case 2:
                    framesLeft--;
                    if (framesLeft <= 0)
                    {
                        PlaySound(soundB);
                        framesLeft = 60;
                        return false;
                    }
                    break;
            }
            return true;
        });
    }
    return true;
});

So yeah, it works, but it’s not really straightforward. More complex tasks will have dozens of states, jumping back to previous states, or you may even have state machines inside state machines inside state machines. I’ve seen this happen more often than I’d like, and it usually results in 20 levels of indentation on a 5000 line method.

There has to be an easier way to do this. How about if the task were something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
tasks.Add(delegate bool() // I happen to prefer this syntax to the lambda one
{
    if (buttonPressed) // Whatever that does
    {
        int framesLeft = 0;
        int state = 0;
        tasks.Add(delegate bool()
        {
            Wait(60);
            PlaySound(soundA);
            Wait(60);
            PlaySound(soundB);
            return false; // ?
        });
    }
    return true;
});

If those Wait() calls block, then the entire task scheduler will stop. However, if they don’t block, then they’re not really waiting. What to do?

A naive solution is to use the OS task scheduler, and create a thread for each task you have, and make the wait calls block. This seems to work, but concurrency becomes a problem. You don’t know when the OS scheduler will switch from task to task, and using variables across threads will require some kind of locking.

There is however, a better way to do this. Some languages (notoriously lua) provide coroutines. That is, a piece of code, that can yield to other pieces of code. In our example, it would be something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
tasks.Add(delegate bool() // I happen to prefer this syntax to the lambda one
{
    if (buttonPressed) // Whatever that does
    {
        int framesLeft = 0;
        int state = 0;
        tasks.Add(delegate bool()
        {
            for (int k = 0; K < 60; k++)
                yield return true
            PlaySound(soundA);
            for (int k = 0; K < 60; k++)
                yield return true
            PlaySound(soundB);
            yield return false;
        });
    }
    yield return true;
});

Notice that instead of return statements, we now have yield return. The idea is that the function temporarily returns a value, but the next time the same piece of code is called, instead of starting once again from the top (like usual functions), it will resume from where we yielded, and every local variable will retain its value.

This creates the illusion of multithreading, without requiring any kind of synchronization, as everything is running on the same thread. This is called “cooperative multitasking”, because you choose when one task yields to another one. This is in contrast to “preemptive multitasking”, in which threads get suspended at arbitrary points.

Unfortunately, you can’t do that in C#. Well, let me rephrase that. You -can- do several things, but it doesn’t get as pretty as I wrote it above.

A way to implement this is with iterators. Instead of a Func<bool>, we wrap it into an IEnumerable<, and yield from it as if it were an iterator. Unfortunately, you can’t yield from inside an anonymous function.

In fact, C# 5 offers a new pair of keyords: async and await, which can be used to create blocks like these, but when I had to solve this problem, C# 5 wasn’t out yet (in fact, I still use Visual Studio 2010), so I needed a solution without these new awesome features.

I found a blog post somewhere (I’ll post it here if I find it again) that suggested faking coroutines using threads, and manually synchronizing them using monitors. Unfortunately, it didn’t provide any code, so I had to come up with a solution of my own.

In the next post, I will show you how to use threads and semaphores to fake coroutines. In order for it to work like real coroutines, we must guarantee that only one thread will be running at a given time, and therefore simulate a cooperative multitasking environment using preemptive multitasking.

Post a Comment

Required fields are marked *
*
*