Quick Thoughts on Continuations for UI Code

December 21, 2007 – 7:00 am

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.

  1. 10 Responses to “Quick Thoughts on Continuations for UI Code”

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

    Miguel

    By Miguel de Icaza on Dec 21, 2007

  3. 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 :)

    By David Mohundro on Dec 21, 2007

  4. 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.

    By James Kovacs on Dec 21, 2007

  5. Sounds very interesting. Could you show us a simple example? :)

    By Marlun on Dec 21, 2007

  6. 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.

    By L.Rawlins on Dec 21, 2007

  7. 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!

    By Rick Brewster on Dec 21, 2007

  8. 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).

    By Dean Harding on Dec 21, 2007

  9. 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.

    By Rick Brewster on Dec 21, 2007

  10. Hey I would also like to see a very simple example. Anyone want to throw one together? Please ;)

    By Mladen Mihajlovic on Dec 22, 2007

  11. Here is one on codeproject
    http://www.codeproject.com/KB/aspnet/YeildContinuations.aspx

    By Edvard Pitka on Mar 24, 2008

Post a Comment