F# – Async Running with Continuation Scissors

As you may have noticed, I’ve been covering a bit about concurrency on this blog lately, and for good reason.  Between Axum, Erlang, Scala and F#, there is a lot to explore with actor model concurrency, task based concurrency, data parallel applications and so on.  In the next couple of months, I have some talks coming up on concurrency and in particular with F#.  I’ve been covering a bit of that with F# and mailbox processing, but I wanted to step back a bit into the asynchronous workflows and hitting against a well known API. 

In this case, let’s walk through a simple example of using the Twitter HTTP API to get a user timeline using F# asynchronous workflows.  Best of all, I’ve done all of the above code without ever once opening up Visual Studio and instead using the power of a simple text editor and F# interactive, I’m able to be quite productive.  It certainly makes me rethink my utter dependence on certain tools…

Give me some tweets… eventually, when you get around to it

In order to get started, we need some helpers when dealing with LINQ to XML.  In a previous post, I talked about the need for such a thing due to the lack of implicit operator support in F#.  We can use the same operator that we had before such as the following:

let inline implicit arg =
    (^a : (static member op_Implicit : ^b -> ^a) arg)

let (!!) : string -> XName = implicit

Alternatively, we could look at utilizing the XNamespace as well due to it also having an implicit operator to convert from a string.  This is helpful as it has a defined the op_Addition which allows us to add an XNamespace to a string to create an XName.  To take advantage we could add an additional operator to take care of this.  Below is a simple session of F# interactive to show how it might work:

> let (!?) : string -> XNamespace = implicit;;
val ( !? ) : (string -> XNamespace)

> (+) !?"";;
val it : (string -> XName) = <fun:it@205>

> !?"" + "foo";;
val it : XName = foo {LocalName = "foo";
                      Namespace = ;
                      NamespaceName = "";}

If you’ll notice above, I curried the XNamespace implicit operator function and sure enough it recognizes it needs a string in order to produce an XName.  Now that we’ve defined our helpers, let’s start looking at how to get a user’s timeline in Twitter.  We’ll need to define a holder for our status data as well as the URL we will be using to post data.

type UserStatus = { UserName : string; ProfileImage : string; Status : string; StatusDate : DateTime }

let timelineUrl = "http://twitter.com/statuses/friends_timeline.xml"

We’re interested at this point just of the user name, their image file location, their status and the date of the tweet.  Now let’s move onto retrieving and parsing the data.

let parseDocument xml =
  let document = XDocument.Parse(xml)
  document.Root.Descendants()
    |> Seq.choose(fun node -> 
         if node.Element(!! "user") <> null then
           let status = HttpUtility.HtmlDecode(node.Element(!! "text").Value)
           let image = node.Element(!! "user").Element(!! "profile_image_url").Value
           let userName = node.Element(!! "user").Element(!! "screen_name").Value
           let statusDate = DateTime.ParseExact(node.Element(!! "created_at").Value, 
                                                "ddd MMM dd HH:mm:ss +0000 yyyy", 
                                                CultureInfo.CurrentCulture)
           Some { UserName = userName; ProfileImage = image; Status = status; StatusDate = statusDate}
         else None)  

let getUserTimeline userName password =
  async { let request = WebRequest.Create timelineUrl :?> HttpWebRequest
          do request.Credentials <- new NetworkCredential(userName, password)
          
          use! response = request.AsyncGetResponse()
          use reader = new StreamReader(response.GetResponseStream())
          let! xml = reader.AsyncReadToEnd()
          
          return parseDocument xml
        }

In the above code, we create a WebRequest, setting the credentials through Basic Authentication.  We’ll cover this approach for now with some attention to OAuth at some point in the relative near future.  After setting the identity, we get the response stream and read it to the end.  This gives us XML to parse and by using LINQ to XML, we’re able to iterate through each node, and if the user node is not null then we parse out each element for which we’re concerned.

Now that we have the ability to do some asynchronous behavior, what do we do about it?  In previous posts, I’ve taken the road of running these operations in parallel, which then runs all instances and returns an answer.  But, if we’re  only executing one instance, how do we enlist in asynchronous behavior overall?  To illustrate, I’ll create a simple Windows Forms application to display the tweets in a textbox and have a refresh button to call the above function to repopulate our textbox.  First, let’s create the basic skeleton:

let form = new Form(Visible=true, TopMost = true, Width=500, Height=300)

let status = new TextBox(Multiline=true, ScrollBars=ScrollBars.Vertical)
form.Controls.Add status
status.Width <- 400
status.Height <- 300

let refresh = new Button(Text="Refresh")
form.Controls.Add refresh
refresh.Left <- 410
refresh.Top <- 10

Now that we have a basic skeleton, let’s add the behavior to the button to refresh our textbox.  To do this, we simply add a handler to the button click event.  In there, we will call Async.RunWithContinuations which gives us the ability to handle not only the successful case, but also should an exception be thrown or some form of cancelation occurs.  Before we look at the implementing code, let’s look at the signature of RunWithContinuations:

> Async.RunWithContinuations;;
val it :
  ('a -> unit) * (exn -> unit) * (OperationCanceledException -> unit) *
  Async<'a> -> unit = <fun:clo@0-42>

What this tells us is that it takes a success continuation as the first argument, the exception continuation as the second argument, the cancelation continuation as the third, and then finally the asynchronous operation.  This allows us to uniquely handle each type of event, should they occur.  In this case, our success continuation should populate the textbox, whereas both the cancelation and the exceptional case should show a message box containing the exception.

refresh.Click.Add
   (fun args ->
      status.Text <- ""
      Async.RunWithContinuations(
        (fun res -> 
           res |> Seq.iter(fun item -> status.Text <- sprintf "User: %s - %s\r\n\r\n%s" item.UserName item.Status status.Text )),
        (fun exn -> MessageBox.Show(sprintf "An error occurred: %A" exn) |> ignore),
        (fun cxn -> MessageBox.Show(sprintf "A cancellation error ocurred: %A" cxn) |> ignore),
        (getUserTimeline "username" "password")))

As you can see, the behavior is rather straight forward to define handlers for our three cases.  Lastly, we provide our credentials to the getUserTimeline function and then when the button is clicked, we’ll get results much like the following screenshot.

image

Then we can keep pressing the refresh button and then asynchronously, it will handle the behavior appropriately, either in success or in failure.  

Conclusion

This is a pretty simply and naive application, but it can show how you can take advantage of asynchronous behavior using the Async Workflows in F#.  By being able to have the F# libraries handle the exceptions and cancelation, a major source of errors is reduced by quite a bit.  Giving us the power then to handle each one of these scenarios, either in success, failure or cancelation is a great concept.  There is still more yet to be covered including Futures and the inclusion of the Task Parallel Library as well as other adventures into Twitter and Bing APIs.

3 Comments

Comments have been disabled for this content.