Coroutines are an extremely powerful technique used to emulate the kinds of features supported by the async/await in .net4.5, but in earlier versions ( c# >= v2.0 ) .
Microsoft CCR (take a read) also employs (employed?) this approach.
Let's get one thing out of the way. yield
alone is not valid and is always followed by either return
or break
.
Think about a standard IEnumerator (that doesn't yield flow control messages).
IEnumerator YieldMeSomeStuff()
{
yield "hello";
Console.WriteLine("foo!");
yield "world";
}
Now:
IEnumerator e = YieldMeSomeStuff();
while(e.MoveNext())
{
Console.WriteLine(e.Current);
}
What's the output?
hello foo! world
Notice how, the second time we called MoveNext
, before the Enumerator yielded "world" some code ran within the Enumerator. What this means is that in the Enumerator, we can write code that executes until it hits a yield return
statement, then simply pauses until someone calls MoveNext
(handily with all state/variables neatly captured, so we can pick up where we left off). After a MoveNext
call, the next bit of code after the yield return
statement can run until another yield return
is reached. So we can now control the execution of the code between the yield return
statements with the MoveNext
call to the Enumerator.
Now, say instead of yielding strings, our Enumerator were to yield a message that says to the caller of MoveNext
, "please hang around for x (waitTime) seconds before you call MoveNext
again". The caller is written to "understand" a variety of messages. These messages will always be along the lines of "please wait for such and such to happen before calling MoveNext
again".
Now we have a powerful means of pausing and restarting code that requires other conditions to be met before it can proceed, without having to write that functionality into another method, like doing async stuff without coroutines. Without coroutines, you're forces to pass around a horrible async state object that you would need to manually assemble to capture state between the end of one method and the starting of another after some async stuff). Coroutines eliminate this because scope is preserved (by compiler magic), so your local variables persist over long lived async stuff.
StartCoroutine
simply starts the whole process. It calls MoveNext
on the Enumerator... some code runs in the Enumerator... The enumerator yields a control message, which informs the code in StartCoroutine
when to call MoveNext
again. This need not happen in a new Thread, but can be handy in multithreaded scenarios because we can call MoveNext
from different threads and control where the work is done.