views:

418

answers:

1

How do I use F#'s built-in support for async operations classes exposing the Event-based Asynchronous Pattern such as WebClient class?

let Download(url : Uri) =
    let client = new WebClient()
    let html = client.DownloadString(url)
    html

When I try to change this to use "let!" in an async block (say as described in Soma's recent post)

let Download(url : Uri) =
    async {
    let client = new WebClient()
    let! html = client.DownloadStringAsync(url)
    return html }

I get an error message:

Type constraint mismatch. The type unit is not compatible with type Async<'a> The type 'unit' is not compatible with the type 'Async<'a>'

Edit: I'm really asking about the general question of using *Async() methods, WebClient is just an easy example. Microsoft says "... you should expose asynchronous features using the Event-based Asynchronous Pattern [ as opposed to BeginFoo()/EndFoo() ] whenever possible ..." so I would think there should be an easy way to consume an arbitrary *Async() method from F#.

+4  A: 

The WebClient.DownloadStringAsync method is part of the .NET framework. It'll raise an event to signal its progress, and its return type is unit, so you don't want to use it, and there's no advantage in wrapping it in an async object.

The F# PowerPack defines an extension method, val webclient.AsyncDownloadString : uri -> Async{string}:

let Download(url : Uri) =
    async {
    let client = new WebClient()
    client.Encoding <- Encoding.GetEncoding("utf-8")
    let! html = client.AsyncDownloadString(url)
    return html }

Unfortunately, the choice of name clashes with the existing webclient method, which can understandably cause confusion. However, I believe all of the F# async extensions begin with Async*.


[Edit to add in response to comments:]

Usually, .NET uses the BeginFoo / EndFoo pattern for concurrency. If the types are right, you can just use Async.BuildPrimitive beginMethod endMethod, which will return an Async wrapper for the method.

Sometimes objects don't use this pattern, like the WebClient, and you actually have to use Async.AwaitEvent to wait for an event to be fired, or write your own loop to repeatedly check to see if a bool is set. Here's a nice article on converting events to Async objects.

For what its worth, if you have F# installed, you should also have the source code which will give you an idea of how the F# team implements their async extensions. On my machine, the relevant file is located at:

C:\Program Files\FSharp-1.9.6.16\source\fsppack\FSharp.PowerPack\AsyncOperations.fs
Juliet
So how would this problem--using an *Async() method--be handled in general? Do I have to write a F# extension method of my own every time?
Dan
Not anymore: http://msdn.microsoft.com/en-us/library/ms228966.aspx "... you should expose asynchronous features using the Event-based Asynchronous Pattern whenever possible ..."
Dan