October 17, 2007

Dream Asynchronicity Results

Steve Bjorg @ 12:13 pm

In previous posts (here and here) I introduced the building blocks for asynchronous programming in MindTouch Dream, our REST web-services framework for .NET and Mono. Now it’s time to dive into the Result and Result<T> classes, which are the cornerstones of our asynchronous programming model.

Example Setup

First, let’s begin by creating a new Result instance:

Result result = new Result();

Second, let’s assume we have an asynchronous method called Write. In Dream, all asynchronous methods follow the same pattern: the last parameter is a Result instance, which is also the return value. For example, our Write method might look as follows:

Result Write(string data, Result result)

Third, let’s invoke our Write method with our Result instance:

Write("hello", result);

Ok, now we’re just missing is the ability to synchronize with the Write operation. The Result instance offers two methods to accomplish this: Wait and WhenDone.

Wait()

Let’s look at the first alternative using Wait:

Write("hellow", result).Wait();

This code will block until Write completes. If an exception was thrown, it will be rethrown by Wait. In other words, this invocation acts like a synchronous call. This is why the Result instance is returned: to enable call chaining. Furthermore, since it’s so easy to get synchronous behavior from asynchronous methods, library programmers don’t need to provide both variants. Also this form of synchronous invocation benefits from the built-in timeout mechanism. In short, it just made life a lot easier on both sides of the equation! :)

WhenDone()

The second alternative uses WhenDone:

Write(myData, result.WhenDone(delegate() {
    result.Confirm();
    ...
}));

- or -

Write(myData, result).WhenDone(delegate() {
    result.Confirm();
    ...
});

This code will not block Instead the continuation will be invoked when the operation completes. Inside the continuation, we call Confirm to rethrow any exception that might have occurred during the asynchronous call. Both uses are virtually identical aside from these differences:

  1. The timeout timer associated with the Result instance starts ticking when WhenDone is invoked. In the first case, this occurs before Write is called, whereas in the second case it occurs afterwards. This means that whatever time Write took to setup its asynchronous operation will be counted towards the timeout in the first case, but not the second.
  2. Two things must happen for the continuation to trigger: the continuation must be registered and the operation must complete. Since in the first case we register the continuation upfront, it is possible that both the operation and the continuation complete before execution returns to the current context. This is not possible in the second case, since the continuation is only registered after the Write has returned.

These differences are minor and generally don’t matter. I only point them out for completeness. I use the second notation as I find it more readable.

Return() and Throw()

The Result class provides two methods for triggering the continuation: Return and Throw. As the names imply, Return indicates successful completion, while Throw indicates an error.

Let’s look at what our Write method might look like on the inside:

Result Write(string data, Result result) {
    new Thread(delegate() {
        try {
            ...
            result.Return();
        } catch(Exception e) {
            result.Throw(e);
        }
    }).Start();
    return result;
}

Using the Result class is straightforward, although there are some good practices to keep in mind. For instance, the first statement inside our thread is a try..catch block. This ensures that uncaught exceptions are forwarded to the continuation. Strictly speaking it’s not necessary to forward exceptions since the timeout of the continuation will kick in eventually, but this will cost valuable time and resources. (Note: if you find this tedious, don’t worry; In my next post, I will introduce helper methods that make it seamless)

Result<T>

So far we’ve looked at the Result class, which doesn’t return a value. In essence, it’s the embodiment of an asynchronous void operation. The Result<T> class has the same methods as the Result class, but it can also return a value. The returned value is available through the Wait method and the Value property.

For example, let’s revise our Write method as follows:

Result<bool> Write(string data, Result<bool> result)

Then our synchronous example would look like this:

bool success = Write("hello", result).Wait();

And our asynchronous example would be:

Write("hello", result).WhenDone(delegate() {
    bool success = result.Value;
    ...
});

Also note that we don’t need to call Confirm anymore. The Value property does this check implicitly.

Caution!

If you call WhenDone or Wait more than once, you will receive an exception. Ditto for calling Return and Throw. The Result instance can only be used once to synchronize the completion and the continuation of an asynchronous operation. Similarly, accessing the Value property before the operation completes will also cause an exception to occur.

Summary

Wow, this post is a lot bigger than I was aiming for, but I felt it was important to capture the 99% use-case in one place. Next time, I’ll showcase methods from the static Async class which provides common asynchronous operations.

6 Comments »

  1. Hi

    it seems that there is a little error in that example :
    Result Write(string data, Result result) {
    new Thread(delegate() {
    try {

    } catch(Exception e) {
    result.Throw(e);
    }
    result.Return();
    }).Start();
    return result;
    }

    It should be

    Result Write(string data, Result result) {
    new Thread(delegate() {
    try {

    result.Return();
    } catch(Exception e) {
    result.Throw(e);
    }
    }).Start();
    return result;
    }

    To avoid the call of result.Return() after result.Throw().

    Bye

    Comment by vdaron — October 22, 2007 @ 12:20 pm

  2. Many thanks! I fixed the post.

    Comment by Steve Bjorg — October 29, 2007 @ 11:35 am

  3. Congratulations on great platform. am really impressed with
    Dream server, so nice and elegant.

    Keep these great technical posts coming…

    Cheers!

    Comment by Srdjan — November 3, 2007 @ 6:49 am

  4. Will do, thx!

    Comment by Steve Bjorg — November 4, 2007 @ 2:31 pm

  5. To me it is necessary to find

    Comment by dedynogscrync — February 24, 2008 @ 3:51 pm

  6. To find what?

    Comment by Steve Bjorg — February 24, 2008 @ 10:02 pm

RSS feed for comments on this post. TrackBack URL

Leave a comment