This is an implementation of coroutines in C# using async. It allows you to use C# to write coroutines in the style of Python and EcmaScript 6. The C# async mechanism handles the transitions between the coroutine and the main routine.
To start a coroutine, call the static Coroutine<TIn, TOut, TFinal>.Start
function and await
the result. The Start
function accepts an async
coroutine function as an argument, and provides it with a Yield Services object. It runs the coroutine immediately, until it yields or exits. It returns the coroutine state that represents what the coroutine did, as an awaitable Task<Coroutine<TIn, TOut, TFinal>.CoroutineState>
object.
The CoroutineState
returned by Start
can be any of three types -- one for when the coroutine yields, one for when it returns a value of type TFinal
, or one for when it throws an exception. It has a property CoroutineStateType
, the type of which is an enumeration of the same name, which can be used to determine what type it is. You can also use is
but it's a bit less clunky to use the enumeration. There is also a Visit
function which calls a different function depending on the type.
CoroutineStateType.Yielded
means you can cast theCoroutineState
to typeCoroutineYielded
(actuallyCoroutine<TIn, TOut, TFinal>.CoroutineYielded
, but I abbreviate here). The value yielded, of typeTOut
, is in the propertyValue
. To continue the coroutine, you must call theSend
function and pass a value of typeTIn
, or you can callSendFault
to cause theYield
function in the coroutine to throw the exception you provide. The result ofSend
orSendFault
is aTask
which will return a newCoroutineState
. Note: TheSend
orSendFault
function can be called only once, after which that particularCoroutineYielded
state is invalid and should be discarded. Use the newCoroutineState
instead.CoroutineStateType.Faulted
means the coroutine terminated with an exception. You can cast theCoroutineState
to typeCoroutineFaulted
and retrieve the exception from theException
property.CoroutineStateType.Completed
means the coroutine completed, returning a value of typeTFinal
. You can cast theCoroutineState
to typeCoroutineCompleted
and retrieve the return value from theValue
property.
To write a coroutine, just write an async
function that takes an IYieldServices<TIn, TOut, TFinal>
as an argument and returns Task<TFinal>
.
The Yield Services object provides an awaitable Yield
function that the coroutine can call. The Yield
function takes an argument of type TOut
and returns a value of type Task<TIn>
.
The Yield Services object also provides YieldFrom
, which runs an inner coroutine as another async
function. The inner coroutine can return a different TFinal
type, but can yield on behalf of the outer coroutine; it has to yield the same TOut
in exchange for the same TIn
. If the inner coroutine terminates with an exception, then YieldFrom
will throw that exception.
A test is included which demonstrates the implementation. Be sure and view the output of the test. (The proper way to do it would have been to collect the outputs into a list and then Assert
that the list is as expected -- but there was a lot of output and I was lazy.)
This project is half of a constructive proof that async and coroutines are duals. (The other half would be an implementation of async in terms of coroutines; I think that's what Python 3.5 does.)
I've tried to implement this idea before, but this implementation is the first I've written that supports "yield from."