Tuesday, May 13, 2008

Coroutines in C#

There are numerous statements that the yield statement in C# uses Coroutines or results in Coroutines. This is not precisely correct. What the yield statement creates are Generators.

To recap some terminology, Generators like Coroutines are generalizations of Subroutines. Both Coroutines and Generators allow multple entry points to allow execution to be temporarly suspended and then later resumed. Whereas a Subroutine have only one point of entry and they return only once.

What distinguishes Coroutines from Generators is what is returned in the yield. Coroutines yield the next Coroutine to be invoked (or resumed). Generators yield a value to be returned to the parent function.

When yield is used it must be within an iterator block or a function which return IEnumerable. As such the resulting function is a Generator and not a Coroutine.

The good news is that Coroutines can be created or emulated from Generators by using a Dispatching function.

Consider the following code in C#:

class Program

{

static List stringBuffer;



enum NextMethod

{

Produce,

Consume

}



static void Main(string[] args)

{

stringBuffer = new List();

Dispatcher();

}



private static void Dispatcher()

{

IEnumerator enumeratorProduce = produceCollection().GetEnumerator();

IEnumerator enumeratorConsume = consumeCollection().GetEnumerator();



IEnumerator enumerator = enumeratorProduce;

while (enumerator.MoveNext())

{

NextMethod next = enumerator.Current;

if (next == NextMethod.Produce)

{

enumerator = enumeratorProduce;

}

if (next == NextMethod.Consume)

{

enumerator = enumeratorConsume;

}

}

}



private static IEnumerable produceCollection()

{

int i = 0;

while (true)

{

stringBuffer.Add(i.ToString());

i++;

yield return NextMethod.Consume;

}

}



private static IEnumerable consumeCollection()

{

int i = 0;

while (true)

{

Console.WriteLine(stringBuffer[0]);

stringBuffer.RemoveAt(0);

yield return NextMethod.Produce;

}

}

}






Ideally we would like produceCollection and consumeCollection to yield control to each other.

Here the same effect is accomplished by yielding an enum that is then interpreted by the Dispatcher function to invoke the correct Generator. If you set breakpoints on all the lines of the code you will see that execution within the Generator routines produceCollection and consumeCollection picks up on the line where it last left off.

permalink

No comments: