Coroutines in C#

I was excited when I learned about yield return in C#, but my excitement quickly waned when I discovered how closely bound it was to the IEnumerable<> paradigm. In my view, it was a missed opportunity to implement a more general coroutine infrastructure, and have IEnumerable<> implemented on top of that. All the hard work of thinking about coroutine implementation has to be done in order to support enumeration, but the benefit is limited to one case—or so I thought.

The other day I came across a real-world case where I wanted some sort of coroutine support. I was writing a long-running process as a Windows service. Separation of concerns meant that there was a service layer that knew about the logic of when to quit, and an implementation layer that didn’t know anything about the environment it was running in. The problem with services is that they need to be fairly responsive to quitting messages, or you get ugly hangs when you attempt to shut the service down.

The business logic needed a fair amount of setup and teardown, so it didn’t make sense for control to leave this code (and lose the state) unless it was fairly certain that a shutdown was imminent. Potentially I could use a callback delegate here, but to me it felt like a coroutine would be a more natural fit.

So we define our inner loop like this:

void DoSomeWork()
{
   while (HaveWorkToDo())
   {
      Work someWork = GetLargeBatchOfWork();

      while (Job nextJob = someWork.GetNextJob())
      {
         DoJob(nextJob);
      }
   }
}

How to get yield return into this? Apart from anything else, we have to decide what type to return. If I was doing an infinite iteration, there’s probably no information that needs to be returned to the caller, so it could return a single value of any type. Boolean true would do the job, or just an instance of an anonymous local class if you’re worried that the caller will read some significance into the boolean. As it was, I had a task that was long-running but not infinite, so it seemed sensible to return a value that indicated this. I created an enumerated type with values Incomplete and Complete.

We can then do something like this for the inner loop:

IEnumerable<Status> DoSomeWork()
{
   while (HaveWorkToDo())
   {
      Work someWork = GetLargeBatchOfWork();

      while (Job nextJob = someWork.GetNextJob())
      {
         DoJob(nextJob);
         yield return Status.Incomplete;
      }
   }

   yield return Status.Complete;
}

In the main loop this can be read in a fairly natural way:

IEnumerable<Status> worker = DoSomeWork();

while (!TerminateNow && worker.Current == Status.Incomplete)
{
   worker.MoveNext();
}

Is this a massive subversion of how yield return is supposed to be used? It looks quite neat to me, though it needs some commenting to explain what’s going on.  I don’t like the way we have to call MoveNext()—nothing is being moved, as such, so the method name doesn’t communicate as much to the reader as it ought to. I’m also concerned that many developers have yet to embrace the lazy nature of IEnumerable<> and may see a function returning IEnumerable<> as a sign that it’s a long-running function that returns when it’s done.

It’s nice to see that this kind of thing can be done, and that the language isn’t constraining me too much. On the other hand, I still wish I had the control to improve the naming, if nothing else.

Leave a Reply

Your email address will not be published. Required fields are marked *