The Lazync Computation Expressions

There is lately quite a lot of fuss over C# 5.0, caused by the new async features. The C# async feature is based on (or was inspired by) the F#’s Async computation expression, which I will now discuss a bit (and then present my improvement).

Usually, async code works in to ways – sequential (wait for task A to finish, then wait for task B to finish, then C, then D, etc.) and parallel (start all tasks and wait for all to finish). If we think about C# again (for the last time), then parallel execution already works using Threading.Tasks and sequential execution is currently available using callbacks (or Begin-End) and soon will be improved with the “await” functionality. In some cases, neither sequential not parallel fit and the code requires some light hacking and combining sequential and parallel execution. The lazync computation expression (which will be introduced soon) aims to give an additional execution strategy which will make the programmers’ lives easier.

Lazync is basically lazy async, meaning that it combines the characteristics of both Lazy and Async. The lazync task can be run asynchronously, like Async, but will not be forced to execute until its value is needed. To understand  better, let’s see a simple example. If we would like to fetch the contents of two websites and compare their source code’s length, we could write this code for sequential async:

let getLength (url : string) =
    async {
        let webclient = new System.Net.WebClient()
        printfn "Before reading %s" url
        let! html = webclient.AsyncDownloadString (new Uri(url))
        printfn "After reading %s" url
        return html.Length

let ``compareSize sequential async`` url1 url2 =
    async {
        let! len1 = getLength url1
        let! len2 = getLength url2
        return compare len1 len2
    |> Async.RunSynchronously

This function (the second one of the two) receives two URLs, fetches each one of them, then compares the lengths. Why isn’t this good? Because while waiting for it to the first web page, we could start fetching the second web page. That’s why Async.Parallel is useful:

let ``compareSize parallel async`` url1 url2 =
    let [| len1; len2 |] =
        [ getLength url1; getLength url2 ]
        |> Async.Parallel
        |> Async.RunSynchronously
    compare len1 len2

This function executes the two tasks (of reading the web pages) independently, and after both are done it compares the lengths. Why isn’t this good? The syntax is awful. In this case, we would want to use the lazync, which would give us the power of parallel with syntax of sequential:

let ``compareSize lazync`` url1 url2 =
    lazync {
        let! len1 = getLength url1
        let! len2 = getLength url2
        return compare len1.Value len2.Value
    |> Lazync.Force

The syntax is the same as in sequential async, except that we use the lazync builder instead of the async one, we use Lazync.Force instead for Async.RunSynchronously, and when using len1 and len2 we need to call their Value properties (like with Lazy<_>). But these small syntax changes completely change what happens. When you use let! (or do! or use!) in a lazync computation, you only start running the async value. The execution will wait for the async value to complete only once it is needed. Since the reading of each web site is independent of the other web site, we will start reading them both and wait for them both when we try to compare their length.

The lazync example we’ve seen can be written using Async.StartAsChild, but it would be less readable and longer.

One great disadvantage of the lazync is that like the sequential async, it works only with fixed number of async values, unlike parallel async, which works with sequences of values (and so is more dynamic). An easy extension to the lazync is the LazyncArray, which gives us the usability of parallel async, but with lazyncs. Even though the comparison is unfair, we’ll start by seeing the sequential async version:

let ``existsTrue sequential async`` args =
    let i = ref 0
    let found = ref false
    while not !found && !i < Array.length args do
        found := Async.RunSynchronously args.[!i]
        incr i

We fake an imperative iteration over the items, executing them one by one until we find a true value. Why isn’t this good? Not only we had to give up on the nice syntax in order to use the dynamic number of async values, but it’ll also take quite a long time to run this function. Also note that in this case the function will perform differently, depending on the order of the arguments. Here is the parallel async version:

let ``existsTrue parallel async`` args =
    |> Async.Parallel
    |> Async.RunSynchronously
    |> Array.exists id

Notice that this time, the parallel version had an easier syntax. Why isn’t this good? Because we have to execute all of the async values, which wastes time. Also, unlike the sequential version, if we have one very long task, we would always need to wait for it to finish, wherever it is in the args sequence. The lazync version gives us a compromise between the two strategies. First, let’s see the source:

let ``existsTrue parallel lazync`` args =
    |> Lazync.Parallel
    |> Seq.exists id

The syntax is similar to the parallel async version, except for one missing line, which is good, because the parallel’s syntax is easier than the sequential’s. What will this function do? It will first start all the tasks, then go over them one by one, until it finds a true. Why is this imperfect? We are executing all the tasks. Why is this better? Obviously, there is a great advantage here over the sequential version, because we can execute the tasks in parallel. The advantage over the parallel async version is a bit more subtle. The difference is that the parallel async version waits for all tasks to finish executing, while the parallel lazync goes over the tasks like the sequential async, waiting for one to finish and return true. This means that it might return before all tasks have finished (but they will still finish, so beware of side-effects in your code!).

In conclusion, I think that there are many cases in which using lazync and parallel lazync can impove both performance and readability. The idea is still fresh for me as well, so I still don’t know if more asynchronous strategies are required, or if these four strategies cover it all.

The source can be downloaded from: here.

This entry was posted on Sunday, August 7th, 2011 at 7:31 PM and is filed under Default. You can follow any responses to this entry through the RSS 2.0 feed. You can skip to the end and leave a response. Pinging is currently not allowed.

3 Responses to “The Lazync Computation Expressions”

  1. Jason Says:

    Useful, thanks

  2. Gustavo Guerra Says:

    This is really cool. You should add this monad to the fsharpx library

  3. Ramon Snir Says:

    Gustavo: you’re welcome to add it!

Leave a Reply