A Coroutine Library in C#

In this post I’m going to introduce an implementation of coroutines written in C#, the code I’m divulging is the result of my initial foraging into this unfamiliar (to me) concept so please bear this in mind if it appears a little preliminary.

The coroutine library provides a mechanism for two or more methods to transfer control to one another in such a way that when a method resumes execution, it does so at the position from which it previously passed control to another method.

The C# language includes support for writing iterator methods which use the yield return operation to return control to the caller in such a way that when re-invoked at a later time, execution resumes at the statement following the yield return. This mechanism is the basis for my implementation of coroutines.

The approach I’ve used is to define an abstract base class which provides a means to call user written iterator methods (henceforth referred to as coroutines) so that they can return the necessary information to enable another iterator method to be invoked. The base class thus does the invocation of the coroutines on our behalf and  masks some involved “plumbing” code which is necessary to make the coroutine mechanism easy to use.

Because coroutines are a set of mutually cooperating methods it is natural to design the coroutine library in such a way that we define the coroutines as methods which are all members of  a class. This class derives from an underlying base class that does the housekeeping necessary for tracking each coroutine’s state. The abstract base class is named Cooperative and all the user need do is derive a class from this and in that derived class implement a set of coroutines.

Here is an example of simple class that leverage Cooperative and defines two coroutines, this will help you see how coroutines actually look before we explore how the underlying implementation is coded:

public class KeyboardCoroutines : Cooperative
{
    private Queue<ConsoleKeyInfo> key_queue = new Queue<ConsoleKeyInfo>();

    public override void BeginProcessing(object Arg)
    {
        StartByActivating(ProduceFromKeyboard,Arg);
    }

    private IEnumerator<Activation> ProduceFromKeyboard(object Arg)
    {
        ConsoleKeyInfo info = Console.ReadKey(true);

        while (info.Key != ConsoleKey.Escape)
        {
            while (key_queue.Count < 10 && info.Key != ConsoleKey.Escape)
            {
                key_queue.Enqueue(info);
                info = Console.ReadKey(true);
            }
            
            if (info.Key == ConsoleKey.Escape)
                yield return Activate(ConsumeFromQueue,1);
            else
            {
                yield return Activate(ConsumeFromQueue, 2);
                key_queue.Enqueue(info);
            }

            Debug.WriteLine("ProduceFromKeyboard sees a result of: " + Result.ToString());
        }
    }

    private IEnumerator<Activation> ConsumeFromQueue(object Arg)
    {
        ConsoleKeyInfo key = key_queue.Dequeue();

        while (key.Key != ConsoleKey.Escape)
        {
            while (key_queue.Count > 0 && key.Key != ConsoleKey.Escape)
            {
                Console.Write(key.KeyChar);
                key = key_queue.Dequeue();
            }

            if (key.Key == ConsoleKey.Escape)
                yield return Activate(ProduceFromKeyboard,3);
            else
            {
                Console.Write(key.KeyChar);
                yield return Activate(ProduceFromKeyboard,4);
            }
            Debug.WriteLine("ConsumeFromQueue sees a result of: " + Result.ToString());
        }
    }
}

You’ll notice right away that a coroutine has a return type of IEnumerable<Activation> and a coroutine passes control to some other coroutine by executing a yield return expression which is a function call to Activate in the base class. The base class therefore enumerates a coroutine and uses the value returned by each iteration to select and call the enumerator associated with the next coroutine to execute. Each coroutine’s execution is temporarily suspended at the point it yields and resumes at the next statement when some other coroutine passes control back to it.

In my next post on this subject I’ll show you the base class implementation and explore some alternative ways to expose this coroutine mechanism.