Blogging seems to be one of those things i feel i
should be doing and with the new year I'm giving it some more
attention.
Microsoft TechEd EMEA 2008 in Barcelona was interesting for
me. Joe Duffy was there talking about his new concurrency book, a
topic that I'm very interested in. A very small but fun session with
a large group of core visual studio developers also gave some
insights into their thought process.
But most of all a small presentation of CCR DSS, a
system made for distributed and concurrent programming that isn't
receiving nearly enough attention, mostly due to it having been
developed within the robotics team.
One of the things i noticed in the presentation was
that they used "yield return" in a way it wasn't designed
for. (the best way to use a feature :)
"yield return" can be used to make a normal
function turn into a generator:
http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx
The compiler does a lot of transformations for a
piece of code that uses a yield return, transformations that can be
used to construct continuations without too much pain.
http://en.wikipedia.org/wiki/Continuation
http://blogs.msdn.com/oldnewthing/archive/2008/08/12/8849519.aspx
When writing server-like programs it is often useful to use continuations to handle a protocol or asynchronous code,
where the thread yields the continuation when an operation would have
to wait for completion. To do this in c# you can yield return a
waitable future of the operation that would block, a top level
handler could then decide which continuation is runnable based on
this future and execute it. This is more powerful than implementing
this using reentrancy because the handler is free to run any
continuation it can not just the top most, also the stack wont be
exhausted because the continuations use the heap to store method
scoped variables.
Example of yield return usage:
void Main() {
Handler.Invoke(Save);
Handler.Invoke(()=>Process("save"));
}
IEnumerable Save() {
using (var file = new File()) {
yield return file.BeginWrite();
file.EndWrite();
}
}
IEnumerable Process(string command) {
if (command ==
"save")
foreach (var y in Save()) yield return y;
}
Simply calling the Save method would construct an
Ienumerable representing the continuation, but not actually invoke
Save, this is a serious risk with this pattern, one that a language
extension for c# could solve.
The use of the foreach with the yield return in
Process is used to pump the waitables, a language extension, for
example "yield Save();" would make this more usable.
In this implementation of Process the foreach yield
pattern isn't needed, but it helps because it places the execution of
Process and Save into the some model, if a simple return was used the
Proces method would complete before Save would be invoked, now Save
gets executed within the flow of Process in the place it is called.
Exception handling and try/finally is somewhat broken
when using yield return, using seems to function, which is surprising
since it uses try/finally internally too.
Some issues with the ordering, synchronization and
locking start to arise when using this pattern, as with any other
system that introduces some form of concurrency.
Clear advantage of this pattern over simply using
threads is that it is explicit where the flow is interrupted, so it
is easy to keep pre/post-conditions consistent.
The future that the continuation yields can be a task
executing on another thread, very handy in places where CPU dependant
tasks can be shown to be safely executable on multiple cores with
little synchronization.
This made me wonder if the OS switches to another
thread when a thread hits a hard page-fault and it needs to do disk
IO, probably yes.