Async Computation Expressions – Resource and Exception Management

For the next part of my coverage of Asynchronous Computation Expressions, I’m going to talk about the things you get for free when you use them.  I was reminded of this during a recent chat with Greg Young about how hard it was during asynchronous operations to notify the controlling thread of an exception.  These asynchronous operations are meant to handle such things. 

Let’s get caught up to where we are today with regards to Async Computation Expressions:


Brief Introduction to Asynchronous Computation Expressions

Asynchronous operations, which use the Async<‘a> type, are basically a way of writing Continuation Passing Style (CPS) programs.  There is a reason I covered that in a previous post here about CPS in regards to Recursion, which is why it’s important to learn these concepts.  These asynchronous computations are computations that call a success continuation should the operation succeed and an exception continuation should it fail.  Together, this provides a managed computation expression where you get a few things for “free”.

Listed below are some of the things you get for “free” from the managed computation expressions.  Each of these will be covered in detail in this post:

  • Exception Propagation
  • Cancellation Checking
  • Resource Lifetime Management

Let’s go into each in detail as it makes the story around the asynchronous computation expressions quite intriguing.  I’ll hold off on a lot of the details as I’ll go into that further in a subsequent post.


Resource Management

Simply put, when you use the “use” keyword inside of the asynchronous computation expression, at the end of the scope, the resource will be disposed.  Even should an exception occur, this still happens.  As you may recall, F# has ways of handling resources through the use of the using function, or the use keyword.

(* val using : ‘a -> (‘a -> ‘b) -> ‘b when ‘a :> System.IDisposable *)
using(File.OpenRead(“file.txt”)) (fun stream -> ())

let main()
  use stream = File.OpenRead(“file.txt”)
  () // Resource closed here

Either I can use the using keyword which then takes a function which takes a single input and returns the result where the input must implement System.IDisposable.  The use keyword is syntactic sugar over the same way of doing this.  Now that we understand this, let’s move onto using this inside of an asynchronous computation expression.  As you may note, we can write our computation using the same style.

let read_file file =
  async {
          use stream = File.OpenRead(file)
          use reader = new StreamReader(stream)
          let text = reader.ReadToEnd()
          return text
        }

Async.Run (read_file “file.txt”)

As you may notice, our resources will be cleaned up just as it hits the return statement and the stream and reader are no longer needed.  Should an exception occur, the resources still will be cleaned up.  I’ll cover that in the next section, what exactly happens there.  But, we also have the ability to bind to asynchronous operations by using the “use!” and “let!” keyword such as the following:

open Microsoft.FSharp.Control.CommonExtensions

let read_file file =
  async {
          use! stream = File.OpenReadAsync(file)
          use reader = new StreamReader(stream)
          let! text = reader.ReadToEndAsync()
          return text
        }

Async.Run (read_file “file.txt”)

Now that we understand the basics, let’s move onto exception management and propagation.


Exception Propagation

One of the harder topics to deal with during asynchronous operations is exception management.  The asynchronous computation expressions add this behavior for free.  If an exception is thrown during an asynchronous step, the exception will then terminate the entire computation and then clean up any resources that were allocated by using the “use” keyword.  The exception is then propagated via the exception continuation back to the controlling thread.  You can also handle these exceptions by using a try/catch/finally block in F# such as this:

open Microsoft.FSharp.Control.CommonExtensions
let read_file file =
  async {
          let! stream = File.OpenReadAsync(file)
          try
            use reader = new StreamReader(stream)
            let! text = reader.ReadToEndAsync()
            return text
          finally
            stream.Close()
        }
Async.Run (read_file “file.txt”)

But, what happens when you get a failure?  Let’s take this code snippet for example:

let square_even n =
  async {
          do if n % 2 <> 0 then failwith “Not even”
          return n * n
        }

let result = Async.Run (square_even 3)

What’s going to happen is that we get a FailureException thrown such as the following:

async_throw

But what about when we run multiple computations in parallel?  Tasks runnning the Async.Run will report any failure back to the controlling thread.  When you run them in parallel, it is non-deterministic which one will fail first.  Only the first failure will be reported, and an attempt will be made to cancel the rest of the computations.  Any further failures are ignored.

let task_even n proc =
  async {
          do if n % 2 <> 0 then failwith proc
          return n * n * n 
        }

let failing_tasks = [ (task_even 3 “1”); (task_even 5 “2”)]
Async.Run(Async.Parallel failing_tasks)

And the result will look like this:

async_parallel_exception 

If you notice, only the first exception was noted.  Had I written this slightly different, the second task could have thrown an exception first and would be noted in the same way.

But, what if you don’t want this exception thrown, but instead handed back to you to deal with in your own way?  By using the Async.Catch combinator, you get a choice whether you get the value of the computation or the exception.  Below is an example of using that concept:

(* static member Catch : Async<‘a> -> Async<Choice<‘a, exn>> *)
let square_even n =
  async {
          do if n % 2 <> 0 then failwith “Not even”
          return n * n
        }

let result = Async.Run (Async.Catch (square_even 3))
(* val result : Choice<int, exn> *)

let print_value (c:Choice<int, exn>) =
  match c with
  | Choice2_1 a -> print_any a
  | Choice2_2 b -> print_any b.Message

print_value result

What I’ve been able to do is catch the exception should it be thrown.  The result is given to me as a choice of either an exception or the real value.  In order to extract the value, I need to go through pattern matching, as the Choice<‘a, ‘b> is actually a discriminated union.  Depending on the input, this program could print either the result, or “Not even”.


Cancellation Checking

Cancellation checking behavior in asynchronous computation expressions is quite similar to Thread.Abort in terms of semantics.  What that means is the following:

  • Cancellation is non-deterministic about whether it will succeed
  • The item that caused the cancellation will be notified whether the cancellation succeeded or not.
  • The finally blocks will all be executed.
  • Cancellation may not be caught, but instead will be thrown as an OperationCancelledException should it have been run through the Async.Run.

Cancellation checks are handled for us in the following ways:

  • At each “let!” “do!” or “use!” bind operation and is checked for the cancellation flag
  • Manually, it can be checked by calling “do! Async.CancelCheck()”
  • If the cancellation flag is set, then the cancellation continuation is called

Below is a simple call with a check for cancellation:

let read_file file =
  async {
          use! stream = File.OpenReadAsync(file)
          use reader = new StreamReader(stream)
          let! text = reader.ReadToEndAsync()
          do! Async.CancelCheck()
          return text
        }

let run_async =
  try
    Async.Run (read_file “file.txt”)
  with
    | :? System.OperationCanceledException -> string.Empty

With this, we could try to cancel the operation using the Async.CancelDefaultGroup method or through an AsyncGroup had you created your function through that.


Wrapping It Up

I hope this opened your eyes to the possibilities with asynchronous computation expressions in F#.  They are very interesting and intriguing parts of F#.  Without deep down knowledge of continuations and monads, you can still write very powerful programs in an asynchronous manner.  These are exciting times as Brian McNamara (F# Team Member) has pointed out, we are getting closer and closer to the CTP of F#.  Download the latest bits for F# and try it out. 

I’m heading off to enjoy some R&R time for a much needed vacation, so this blog will be quiet for a little bit.

This entry was posted in Concurrency, F#, Functional Programming. Bookmark the permalink. Follow any comments here with the RSS feed for this post.