One of the most common complaints I hear about Silverlight is it’s Async only networking capbilities. It’s not that people hate Async it’s that they hate how it affects the synchronous flow of their code. We’re used to code that is written where A –> B –> C but when it’s async it might be completely out of order. Continuation Passing Style is one nice way to accomplish a synchronous flow of code while actually being asynchronous in execution, however this imposes on us some extra syntax that can appear very verbose and visually confusing. Here is an example:
Action C = () => Console.WriteLine("C!"); Action<Action> B = b => { Console.WriteLine("B!"); b(); }; Action<Action> A = a => { Console.WriteLine("A!"); a(); }; A(() => B(() => C()));
If you imagine that each of these methods are performing their work (e.g. calling Console.WriteLine) asynchronously and then calling in the passed in method when their asynchronous work was complete this would be a Continuation Passing Style. Not at all logically synchronous.
A few times now I’ve heard people say that it was possible to do multi-threading in C# using iterators in such a way that it could look nicer than the CPS (Continuation Passing Style) method. Specifically a video on the Power Threading library and Miguel de Icaza’s latest blog post.
Watching and reading these makes sense to me but I just couldn’t quite wrap my brain around it. Looking into Jeffery Richters Power Threading library didn’t help too much either I’m afraid. It’s probably really awesome but I couldn’t understand what was going on very easily. The API is pretty verbose and the samples are dense. That’s ok but I like to start things out a little slow, so I created a very basic sample of how to do something asynchronous and synchronize it all easily for the caller with an iterator.
Here is the method that does the real work:
public IEnumerator<int> DoWork(ISynchronizer synchronizer, params string[] urls) { yield return urls.Length; foreach (string url in urls) { WebClient client = new WebClient(); client.DownloadStringCompleted += (o, e) => { Console.WriteLine("{0}: {1}", e.UserState, e.Result.Length); synchronizer.Set(); }; client.DownloadStringAsync(new Uri(url), url); yield return 1; } }
This feels pretty synchronous I guess. Foreach url, print out some string. When this method is done printing out all of the URLs then our work is done. The flow is logically synchronous (except for that wonderful DownloadStringCompleted lambda) but you could imagine how it might be possible for the WebClient to do the same thing if it wanted to.
This method gets effectively turned into a Finite State Machine by the C# compiler. If I look at it in Reflector I can see some really gnarly code I’m glad I didn’t have to write myself! So now to synchronize this. Here is my actual application:
Example e = new Example(); Synchronize.Run( s => e.DoWork(s, "http://www.justnbusiness.com", "http://www.codeplex.com", "http://www.google.com")); Console.WriteLine("Done!"); Console.ReadKey(true);
Which yields the results:
http://www.justnbusiness.com: 43210
http://www.google.com: 6290
http://www.codeplex.com: 34070
Done!
Notice how codeplex.com is returning after google.com even though it is earlier in the list but “Done!” is still happening last.
Effectively what I did was simply create a method that iterates over each call to MoveNext() on the enumerator and gets the largest value yielded. Whatever the largest number is is how many times Set() should be called on the synchronizer before continuing on. Here is the Synchronizer code:
public delegate IEnumerator<int> SynchronizeMethod(ISynchronizer synchronizer); public interface ISynchronizer { void Set(); } public static class Synchronize { public static void Run(SynchronizeMethod method) { Syncrhonizer synchronizer = new Syncrhonizer(); IEnumerator<int> enumerator = method.Invoke(synchronizer); while (enumerator.MoveNext()) { int current = enumerator.Current; synchronizer.SetCurrent(current); } synchronizer.Wait(); } private class Syncrhonizer : ISynchronizer { ManualResetEvent mre = new ManualResetEvent(false); int count; int max; public void SetCurrent(int current) { if (current > max) max = current; } public void Set() { count++; if (count >= max) mre.Set(); } public void Wait() { mre.WaitOne(); } } }
You could envision nested calls to Synchronize.Run in order to synchronize multiple layers of tasks. This is a very simple approach and I could see how this could easily get more complicated. I’m interested in playing around with this a little more though.
If I was in Silverlight this wouldn’t work however. Silverlight won’t let you block the main thread with a ManualResetEvent. So you could probably create a RunAsync overload that accepts a callback to call. So you can free up the main thread.
Personally I’m not sure this is any better but it’s interesting. I’d really like to give it a try once for something more complicated to see how it holds up.
Follow up: The new “async” and “await” keywords in C# basically do exactly this, except the syntax is a million times better. Don’t try to do the above.