Please note: this is only “playing around” code. Do not use this in production!

There isn’t that much to it. We define three co-routines with slightly different behavior to make it a little interesting: FirstCoroutine yields twice, SecondCoroutine yields three times, and ThirdCoroutine yields once.

usingSystem;usingSystem.Threading.Tasks;partialclassProgram{// The first co-routine yields twice.privatestaticasyncTaskFirstCoroutine(){Console.WriteLine("Starting FirstCoroutine");Console.WriteLine("Yielding from FirstCoroutine...");awaitTask.Yield();Console.WriteLine("Returned to FirstCoroutine");Console.WriteLine("Yielding from FirstCoroutine again...");awaitTask.Yield();Console.WriteLine("Returned to FirstCoroutine again");Console.WriteLine("Finished FirstCoroutine");}// The second co-routine yields three times.privatestaticasyncTaskSecondCoroutine(){Console.WriteLine(" Starting SecondCoroutine");Console.WriteLine(" Yielding from SecondCoroutine...");awaitTask.Yield();Console.WriteLine(" Returned to SecondCoroutine");Console.WriteLine(" Yielding from SecondCoroutine again...");awaitTask.Yield();Console.WriteLine(" Returned to SecondCoroutine");Console.WriteLine(" Yielding from SecondCoroutine again...");awaitTask.Yield();Console.WriteLine(" Returned to SecondCoroutine again");Console.WriteLine(" Finished SecondCoroutine");}// The third co-routine yields once.privatestaticasyncTaskThirdCoroutine(){Console.WriteLine(" Starting ThirdCoroutine");Console.WriteLine(" Yielding from ThirdCoroutine...");awaitTask.Yield();Console.WriteLine(" Returned to ThirdCoroutine");Console.WriteLine(" Finished ThirdCoroutine");}}

To run the co-routines exclusively, we create a TaskFactory wrapping a ConcurrentExclusiveSchedulerPair.ExclusiveScheduler. We also create a convenience method RunCoroutineAsync, which takes a co-routine delegate and executes it on that scheduler.

usingSystem;usingSystem.Threading.Tasks;partialclassProgram{staticvoidMain(string[]args){vartask=MainAsync();task.Wait();Console.ReadKey();}/// <summary>/// A task factory using an exclusive scheduler./// </summary>privatestaticTaskFactorycoroutineFactory=newTaskFactory(newConcurrentExclusiveSchedulerPair().ExclusiveScheduler);/// <summary>/// Executes a co-routine using an exclusive scheduler./// </summary>privatestaticasyncTaskRunCoroutineAsync(Func<Task>coroutine){awaitawaitcoroutineFactory.StartNew(coroutine);}/// <summary>/// Starts three co-routines and awaits for them all to complete./// </summary>staticasyncTaskMainAsync(){varcoroutines=new[]{RunCoroutineAsync(FirstCoroutine),RunCoroutineAsync(SecondCoroutine),RunCoroutineAsync(ThirdCoroutine),};awaitTask.WhenAll(coroutines);}}

The tricky part in this code is the double-await in RunCoroutineAsync. This is a normal pattern when you use TaskFactory.StartNew with asynchronous delegates (alternatively, you could use Task.Unwrap).

Logically, the “coroutine” parameter to RunCoroutineAsync is an asynchronous delegate (referring to one of the async co-routine methods). When we pass it to StartNew, we get back a Task<Task> representing the starting of that asynchronous delegate on our exclusive scheduler. The inner task represents the completion of that asynchronous delegate. So the await await is used because we want RunCoroutineAsync to complete only when the asynchronous delegate completes.

If we execute this program, we can clearly see the co-routine behavior:

Starting FirstCoroutine
Yielding from FirstCoroutine...
Starting SecondCoroutine
Yielding from SecondCoroutine...
Starting ThirdCoroutine
Yielding from ThirdCoroutine...
Returned to FirstCoroutine
Yielding from FirstCoroutine again...
Returned to SecondCoroutine
Yielding from SecondCoroutine again...
Returned to ThirdCoroutine
Finished ThirdCoroutine
Returned to FirstCoroutine again
Finished FirstCoroutine
Returned to SecondCoroutine
Yielding from SecondCoroutine again...
Returned to SecondCoroutine again
Finished SecondCoroutine

Just one final word. There are benign race conditions in this code: e.g., it’s possible that FirstCoroutine may run and yield to itself before SecondCoroutine even starts. The ExclusiveScheduler does not make guarantees about queueing or fairness (though it does try to be fair) - it only guarantees exclusive scheduling.