October 8, 2007

Dream Asynchronicity Basics

Steve Bjorg @ 11:54 pm

MindTouch Dream is engineered to scale. A good way to preserve system resources when scaling is to use asynchronous operations when possible. Unfortunately in .NET, asynchronous operations tend to be hard and error prone. But with Dream, we introduced some very capable abstractions that restore sanity without compromising efficiency.

The two key tools offered are the Result and Coroutine classes. The former is the basic building block for coordinating with asynchronous operations and their results, the latter is a library of methods to use co-routines in C#. In this post, we’ll explore the Result class.

Result and Result<T>

The best way to learn how the Result instances work is by example. Let’s say we have a method bool Print(string text) that is synchronous. Now, let’s convert Print() into an asynchronous method. In order to do so, we’ll have to return the final bool result via an asynchronous mechanism. We also need to be able to report any exception that may occur during the execution. Both these operations are accomplished with the Result class.

The new method signature will be Result<bool> Print(string text, Result<bool>). Here is how we would now invoke the new Print method:

Result<bool> result = Print("Hello World!", new Result<bool>());

The Result class has some very handy methods. One of them makes it really easy to invoke an asynchronous method in a synchronous fashion. You may wonder why that is useful? It means that as a library writer, you only need to provide the asynchronous version of a method since the synchronous version can be derived easily.

Let’s see what this looks like:

bool success = Print("Hello World!", new Result<bool>()).Wait();

That’s it! All you need to do is invoke Wait() on a Result instance and the code will block until the operation completes and returns its result. If the operation failed and threw an exception, Wait() will throw the exception as well.

While it might be comforting to see how to use an asynchronous result in a synchronous fashion, it isn’t particularly useful if our goal is asynchronous programming! Now, let’s see how we handle the asynchronous case instead:

Result<bool> result = Print("Hello World!", new Result<bool>());
result.WhenDone(delegate() { Console.WriteLine("Result: {0}", result.Value); });

A few things need to be said here:

1) When accessing the Value property, the Result instance will make sure that the asynchronous operation completed. If it has, it will check if an exception occurred. If the operation completed and no exception occurred, the value is returned, otherwise an exception is raised.

2) Because Print() returned a Result instance, we can chain the calls together like this:

Print("Hello World!", new Result<bool>()).WhenDone(delegate(Result<bool> result) {
	Console.WriteLine("Result: {0}", result.Value);
});

You’ll note that this time, the anonymous method takes a Result parameter so that the value can be read. This trick tends to keep code more readable (assuming you get used to method chaining — which is used extensively in Dream — and you follow consistent code formatting guidelines).

3) Finally, you may wonder why Print() requires a Result instance to be passed in instead of creating its own. The reason is pragmatic. Timeout durations are specified when creating the Result instance. This avoids the need for each asynchronous method to take a timeout parameter. Asynchronous operations without a timeout are the best way to waste memory and force an eventual shutdown of your application. Don’t forget that it’s impossible for the system to tell the difference between a very long operation and one that never returns. Hence, the garbage collector can’t release the memory unless the operation completes in some fashion.

Conclusion

One of the big downsides of asynchronous programming is that each execution stage is its own method. With C# 2.0, this problem was greatly alleviated thanks to anonymous methods. As shown above, anonymous methods save a lot of space and make handling of shared state a lot easier. However, they still lead to ugly code that looks like nested Russian dolls. Consider the following example, where we use the Print() method twice.

Print("Hello World!", new Result<bool>()).WhenDone(delegate(Result<bool> result) {
	Print("Result: {0}", result.Value, new Result<bool>()).WhenDone(delegate() {
		Console.WriteLine("done";
	});
});

Chaining asynchronous calls in a row leads to hard to read code. But don’t despair! In a latter blog post, we’ll examine the Coroutine class, which makes asynchronous code look a lot more like synchronous code.

1 Comment »

  1. [...] previous posts (here and here) I introduced the building blocks for asynchronous programming in MindTouch Dream, our [...]

    Pingback by Dream Asynchronicity Results | MindTouch Blog — October 17, 2007 @ 12:13 pm

RSS feed for comments on this post. TrackBack URL

Leave a comment