Coroutines: Building a framework in C#

posted in Vaerydian
Published February 13, 2014
Advertisement
Where to start?
So we want to build a Coroutine, where do we start? Firstly, and simply, choose a language which supports some sort of re-entrant behavior natively. C/C++ unfortunately don't really support native re-entrant behavior at least not in any clean way. Though its possible somewhat with goto's or an odd duff's device, the code can get ugly and cumbersome quickly. Luckily, C# does support this natively through iterators.

Understanding C# Iterators
If you've used C# to any degree, it is highly likely you've used an iterator before, likely without knowing it. One very common usage is via the "foreach" keyword usually over a collection of some sort like a list. such as:List foo = new List();foo.add(1);foo.add(2);foo.add(3);foreach(int i in foo){ Console.WriteLine(i);}
But how does that use iterators?
Most collections use iterators by implementing the IEnumerable or IEnumerator interfaces. These interfaces provide the backbone into the C# guts that allows re-entrant programming constructs to be created. The IEnumerable construct is what powers the re-entrant behavior with IEnumerator providing the majority of the access mechanisms. Combined, with some clever scaffolding, allows for the creation of Coroutines. Before we delve into Coroutines, we'll cover the basics of IEnumerable and IEnumerator.

How does IEnumerable Work?
IEnumerable works in 2 default ways IEnumearable and IEnumerable. We're only interested in the latter implementation IEnumerable. This interface (the 'I' in "IEnumerable") tells the CLR (.Net Runtime) that the following code is enumerable (i.e. iteratable). Meaning that it will produce values upon each successive call. You as the programmer get to specify those values utilizing the special enumeration keywords "yield return" and "yield break". It works kinda like so:public IEnumerable enumerable_function(int a){ int b = 0; while(true){ if(b > 100) break; b += 1; yield return a + b; } yield return b / a;}
with this demonstration, we yield "a + b" until "b > 100" at which point we then yield "b / a". Notice I don't use a "yield break" in this example. "yield break" is a way to "terminate" an IEnumerable early, but is more of a keyword to the CLR that this "enumeration" is "done". This is useful when you want the logic of your IEumerable method to terminate right then and there and not yield any new information. Also, "yield break" does not "yield" a value, it just terminates.

How does IEnumerator work?
IEnumerator represents the "calling" mechanism of the CLR for enumerable constructs. This is more of the way that the CLR utilizes the IEnumerable method or class. In this case, what is most important is that a IEnumerator is created from an IEnumerable, meaning a IEnumerator requires the existence of an IEnumerable construct. That said, the IEnumerator exposes two key access mechanisms: the "MoveNext()" method and the "Current" property. The "MoveNext()" method is your main "re-entrant" executor. Meaning, it calls the method defined by IEnumerable to re-enter and execute until its next "yield return". The "Current" property, makes available the last yielded value from the IEnumerable method. In this case, demonstrated as so:public void do_stuff(){ IEnumerator foo = bar.GetEnumerator(); foo.MoveNext(); Console.WriteLine(foo.Current);//print 1 to console foo.MoveNext(); Console.WriteLine(foo.Current);//print 5 to console foo.MoveNext(); Console.WriteLine(foo.Current);//print 10 to console}public IEnumerable bar(){ yield return 1; yield return 5; yield return 10;}
So how do we make a Coroutine with these?
Well, like many things, you build it with abstraction. In this case, a specially constructed abstract class. So, how doe we structure this beast? First, lets setup a basic structure, we need a Coroutine class and a standard method we'll call for our "Coroutine code". Additionally, in order to keep things simple and non-confusing, we'll not implement any Generics and instead just use return objects. The first setup will look like so:public abstract class Coroutine{ public abstract IEnumerable process();}
So we have our basic "atomic" Coroutine structure. This class is actually somewhat usable right now. If you built a class of of this, you could use it abstractly like so:int main(){ Coroutine foo = new Coroutine(); IEnumerator bar = foo.process.GetEnumerator(); bar.MoveNext(); Console.WriteLine(bar.Current); }
Even if we used it that way, it would get cumbersome, so lets "enhance" this Coroutine with some more usable access mechanisms. In this case, lets add a "next" method, that abstracts away both the "MoveNext" method as well as the "Current" property, such that when called, it re-enteres the coroutine and then returns the next value. It would look like so:public abstract class Coroutine{ private IEnumerator _enumerator; public Coroutine(){ this._enumerator = this.process.GetEnumerator(); } public object next(){ this._enumerator.MoveNext(); return this._enumerator.Current; } public abstract IEnumerable process();}
Yay! now we have something a bit more easy to utilize. Utilizing the previous example, we would now write it as so:int main(){ Coroutine foo = new Coroutine(); Console.WriteLine(foo.next()); }
Nice huh?

But wait, we're not done yet!
So, we've got a good base framework for a Coroutine. It works, you can write a stand-alone coroutine without problems. But what if you wanted to yield execution to another Coroutine during execution? This current framework wouldnt really help, would it? As this Coroutine sits, it is fairly simple. We'll need to enhance it by a bit if we're going to add that juicy functionality.

So how do we do that?
Well, first we need a way to tell the core abstract Coroutine class that its should execute another Coroutine. So we'll need to set a flag of some such as a mechanism to specify the coroutine to begin yielding. We'll also be required to keep some sort of reference to the other coroutine, a "sub coroutine" as it were. What would this look like? It would look like so:public abstract class Coroutine{ private IEnumerator _enumerator; private bool _do_sub = false; private Coroutine _sub_coroutine; public Coroutine(){ this._enumerator = this.process.GetEnumerator(); } public object YieldFrom(Coroutine coroutine){ this._do_sub = true; this._sub_coroutine = coroutine; return this._sub_coroutine.next(); } public object next(){ if(_do_sub){ return this._sub_coroutine.next() }else{ this._enumerator.MoveNext(); return this._enumerator.Current; } } public abstract IEnumerable process();}
So how would I use that?
Lets use it to define two coroutines, and have one yield to another. We'll keep it as simple as possible.public class Foo: Coroutine{ public override IEnumerable process(){ yield return 3; yield return 4; yield return 5; }}public class Bar: Coroutine{ public override IEnumerable process(){ yield return 1; yield return 2; yield return YieldFrom(new Foo()); yield return 6; }}int main(){ Bar bar = new Bar(); Console.WriteLine(bar.next());//prints 1 Console.WriteLine(bar.next());//prints 2 Console.WriteLine(bar.next());//prints 3 Console.WriteLine(bar.next());//prints 4 Console.WriteLine(bar.next());//prints 5 Console.WriteLine(bar.next());//prints 5 *huh? Console.WriteLine(bar.next());//prints 5 *wait? Console.WriteLine(bar.next());//prints 5 *uh oh Console.WriteLine(bar.next());//prints 5 *oh noooo!!!}
Did you catch the problem?

Yea... how are we going to fix that?
How will our 'parent' Coroutine know when its 'child' is done? Well, this is where things get a little complicated. This is where introducing another Coroutine construct becomes helpful. In this case, we'll introduce a method called "YieldComplete". It will set flags that state that "this coroutine" is "done". So instead of using something like "yield break", you'll call "yield return YieldComplete(object)". Now our coroutine starts looking like this:public abstract class Coroutine{ private IEnumerator _enumerator; private bool _do_sub = false; private Coroutine _sub_coroutine; public bool is_complete = false; public Coroutine(){ this._enumerator = this.process.GetEnumerator(); } public object YieldFrom(Coroutine coroutine){ this._do_sub = true; this._sub_coroutine = coroutine; return this._sub_coroutine.next(); } pulbic object YieldComplete(object return_value=null){ this.is_complete = true; return return_value; } public object next(){ if(_do_sub){ if(!this._sub_coroutine.is_complete) return this._sub_coroutine.next(); else this._do_sub = false; this._enumerator.MoveNext(); return this._enumerator.Current; }else{ this._enumerator.MoveNext(); return this._enumerator.Current; } } public abstract IEnumerable process();}
Gets a little more ugly, but now our previous example will work just fine!public class Foo: Coroutine{ public override IEnumerable process(){ yield return 3; yield return 4; yield return YieldComplete(5); }}public class Bar: Coroutine{ public override IEnumerable process(){ yield return 1; yield return 2; yield return YieldFrom(new Foo()); yield return 6; }}int main(){ Bar bar = new Bar(); Console.WriteLine(bar.next());//prints 1 Console.WriteLine(bar.next());//prints 2 Console.WriteLine(bar.next());//prints 3 Console.WriteLine(bar.next());//prints 4 Console.WriteLine(bar.next());//prints 5 Console.WriteLine(bar.next());//prints 6 *yay!}
So is that all to it?
Not quite. There is still a weird edge case in there you need to catch in regards to if MoveNext() should be called to prevent an odd double value return scenario when switching back from a child to a parent. That can be fixed by tracking the boolean output of MoveNext() and checking it when you're also checking "is_complete". Additionally, its also nice to be able to actually pass INPUT to a coroutine ;) That also requires a few more widgets to be added. Nothing terrible, but you now need to store and maintain the last "input" value passed. Combining those two together, gets you the following:public abstract class Coroutine{ private IEnumerator _enumerator; private bool _do_sub = false; private Coroutine _sub_coroutine; public bool is_complete = false; public bool can_move_next = true; private object _sub_input = null; public Coroutine(){ this._enumerator = this.process.GetEnumerator(); } public object YieldFrom(Coroutine coroutine, object sub_input=null){ this._do_sub = true; this._sub_coroutine = coroutine; this._sub_input = sub_input; return this._sub_coroutine.next(); } pulbic object YieldComplete(object return_value=null){ this.is_complete = true; return return_value; } public object next(object in_value=null){ if (this._do_sub) { if (this._sub_coroutine.can_move_next && !this._sub_coroutine.is_complete) return this._sub_coroutine.next (this._sub_input); else { this._do_sub = false; this._input = in_value; this.can_move_next = this._enumerator.MoveNext (); return this._enumerator.Current; } } else { this._input = in_value; this.can_move_next = this._enumerator.MoveNext (); return this._enumerator.Current; } } public abstract IEnumerable process();}
Now you have a very usable Coroutine construct! You can easily call it via next(), pass input, and yield to other coroutines! yay!

Is there anything else we should do?
Well, we do use a lot of IEnumerable and IEnumerator data... so what if we wanted to turn it up to 11? We could easily make this work in the loop constructs, by having Coroutine itself inherit from IEnumerator and then adding core MoveNext(), Current, etc. If you did that, you could do things like this:public class Foo: Coroutine{ public override IEnumerable process(){ yield return 3; yield return 4; yield return YieldComplete(5); }}public class Bar: Coroutine{ public override IEnumerable process(){ yield return 1; yield return 2; yield return YieldFrom(new Foo()); yield return 6; }}int main(){ Bar bar = new Bar(); foreach(int i in bar){ Console.WriteLine(i);//prints 1, 2, 3, 4, 5, 6 }}
Slick huh? I'll leave you to explore things over at AsyncCS if you're curious how to lay that foundation (its really simple btw).

So Now What?
Well, that is how easy it is to build a basic Coroutine framework. In my next journal entry I'll go over how to take this framework and expand it into a thread worker model (very simple) and then use that to implement a very simple HTTP server. If you're curious, you can check out the AsyncCS link above if you want to play around with it. Take note that it is still experimental and does rely on my custom testing framework CSTester (works very similar to NUnit, but is much simpler and lighter weight), but it should be easy enough to exclude that subproject if you don't want it.

Anyway, that's all for now!
3 likes 2 comments

Comments

evolutional

Interesting concept; however I'm struggling to see why you would want to implement such a thing in .NET. In .NET 4 and especially 4.5 we now have the Task Parallel Library (TPL) and async/await. This makes async programming mostly trivial (with caveats, obviously). Async/Await and TPL grants you pretty much everything you are trying to implement here.

TPL can be built into nice fluent piplelines (eg: TPL DataFlow) and can be coupled with Observables using the Reactive Extensions (Rx) framework for some really great event driven activities. Alternatively, you can utilise Func<Task> based callbacks for some awesome async / couroutine style servers.

February 17, 2014 10:41 PM
NetGnome
Oh I have no delusions of grandeur with this library. Its just an experiment showing how to create Coroutines for those curious. The bigest difference between coroutines and await/async is re-entrancy (if that is a word). Coroutines get to re-enter, while async/await "run a task asynchronously until complete". In this way, the async/await structure is more akin to the YieldFrom covention or to a very streamlined callback implementation. In this way, coroutines offer the advantage in that you can specify more fine grained behavior in your code or allow you to use non-async code in asynchronous ways such as helping to make Socket.Send() asynchronous as the await/async version doesn't exist yet as it uses the old async model.

Coroutines are not all sunshine and lolipops either, I have noticed one annoying flaw in the way the compiler treats enumerations, you can't yield inside of a "try-catch" block... which can make some errors more annoying to come across. They are also not simply made generic (why I used object returns), so the easiest way to use them is with object casting... but that makes things from a maintenance standpoint poorer in the long-run.

So just like everything, there are pros and cons, but I found it fun to work on from an experimental perspective smile.png

I should have the simple http server journal entry out later this week, its a longer one for sure, but it shows how one could apply coroutines to make more complex systems than the simple examples thus far.
February 18, 2014 10:54 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement