Quick Thoughts on Continuations for UI Code

Continuations in C#, as exposed via the yield keyword and “iterator blocks”, is a seriously underused and under-researched feature. In C++, you can use Win32 fibers to accomplish the same, and this is in fact how they are implemented in the .NET 2.0 runtime. Update: They are not implemented using Fibers. Thanks for the clarification James. Learn something knew every day!

Did you know that you can use these to implement blocks of code that can literally jump between threads? Quite useful for UI code so you can avoid using all sorts of tedious synchronization and messaging primitives, and also avoid having to split your tasks into multiple functions. Instead of writing function 1 which kicks off a task on the thread pool, then function 2 that performs the long-running task (load a file, download some data, compute something useful), and then function 3 that executes on the UI thread when that is done and sets some UI flags … you can write 1 function that is hosted by a dispatcher or executor of some sort, and then use “yield return” to spit instructions at it. Need to report progress? No problem. Switch to the UI thread, tell your ProgressBar control what’s up, then switch back to the background thread. “yield return new SwitchTo(Thread.UI)” and “yield return new SwitchTo(Thread.Background)” are going to prove quite handy for Paint.NET v4.0.

I’ve been experimenting with this concept a little bit, and the results are promising. I have a simple application that has a “AsyncImageBox” control that uses continuations. It has one function, “LoadTheImage” which implements all of the logic for establishing a connection with a server, downloading the image, reporting progress in the UI, and hooking up the final image to the UI. When the block of code needs to do something that must be done on the UI thread (report progress), it just switches over to the UI thread and does it. Then it reverts to the background thread. This is all inside of 1 function of code, there is no “queue this to the thread pool” or “invoke this message over to the other thread” or “wait on this mutex” or “set this signal” nonsense. I can use traditional synchronous/blocking code (aka, “easier to write, read, debug, maintain, understand, etc.”) for all of this.

10 thoughts on “Quick Thoughts on Continuations for UI Code

  1. Miguel de Icaza says:

    Using the yield keyword would be a portable implementation, using fibers through P/Invoke would make the code highly unportable

    Miguel

  2. David Mohundro says:

    My only experience with the yield keyword is essentially returning an IEnumerable. Your post clearly shows me that I have a lot to learn about its capabilities…

    Would you mind posting the AsyncImageBox code or sharing a link to some code that is doing this? I’m not a huge fan of the BeginInvoke/EndInvoke pattern 🙂

  3. James Kovacs says:

    Continuations in C# 2.0/3.0 are not implemented using Win32 fibers. (There was a MSDN article on the subject that used fibers to implement continuations in .NET 1.1, which is what you might be remembering.) In C# 2.0/3.0, continuations are done via “C# compiler magic”, which generates an inner class implementing an IEnumerable-based state machine. Reflector a class that uses the “yield” keyword and you’ll see what I mean. This is also why continuations are a C# feature and not a CLR feature. It’s all just C# syntactic sugar. Cool and under-used syntactic sugar, but syntactic sugar nonetheless.

  4. L.Rawlins says:

    I could have sworn English was my first language…

    But whatever you’ve just said, I’m sure it was poignant to those who understood it! Haha!

    Merry Christmas Mr. Brewster.

  5. Rick Brewster says:

    Miguel – Not to mention that the CLR will throw a fit if you just start executing code in a thread context that it doesn’t know anything about!

    James – I could’ve sworn I read a blog post or posts on blogs.msdn.com that said otherwise, but you appear to be correct. Hmm, I wonder why they didn’t just use fibers. I can’t imagine it was cheap to develop this different solution!

  6. Dean Harding says:

    I imagine they didn’t use fibers because the yield return keyword does not really implement “continuations” as such. You can do fancy stuff like what you’ve been doing with a “SwitchTo” class or something, but it wouldn’t make sense to implement yield return with fibres.

    Remember, the basic usage pattern of yield return is:

    public void IEnumerator MyEnumerator()
    {
    for(int i = 0; i < 10; i++) {
    yield return i;
    }
    }

    then,

    foreach(int i in MyEnumerator()) {
    Console.WriteLine(i);
    }

    That is, it’s syntatic sugar for writing enumerators. Using fibres would be very much overkill. Fibres also introduce limitations to the runtime that make them unworkable in all but the most resticted environments (e.g. inside SQLCLR or something).

  7. Rick Brewster says:

    Dean – Yeah I’ve seen both “continuations” and “coroutines” thrown around to describe this features, and I’m still not completely sure (or I forget) which one is the closer term.

    In any case I’m seeing it as a very useful way to implement code that would otherwise have to be split into multiple functions with lots of arduous and manual synchronization and messaging logic.

Comments are closed.