Adding Async Operations to Asynchronous Computation Expressions in F#
Asynchronous Computation Expressions are an extremely powerful feature in F#. It's important to not only know how to use them, but also to extend the behavior so that other classes can bind and perform asynchronous behavior. What I want to show in this post is how easy it is to add this behavior to any custom web service that you may create.
In Review
We saw earlier in my post about Task Parallel Library and Async Computation Expressions, I briefly mentioned how I added some capabilities back to the WebRequest in the form of GetResponseAsync(). Let's take a look at the code involved for that to make it happen, as well as the code that uses it.
open System.IO
open System.Net
open Microsoft.FSharp.Control.CommonExtensions
type System.Net.WebRequest with
member x.GetResponseAsync() =
Async.BuildPrimitive(x.BeginGetResponse, x.EndGetResponse)
let download(url:string) =
async { let request = WebRequest.Create(url)
use! response = request.GetResponseAsync()
use stream = response.GetResponseStream()
use reader = new StreamReader(stream)
let! html = reader.ReadToEndAsync()
return (url, html)
}
What I'm doing is adding an extension method to the System.Net.WebRequest class to add the GetResponseAsync() function. In order to create an extension method, property, static method and so on, it's pretty simple. The function BuildAsyncPrimitive takes in not only the parameters of your function, but the Begin and End functions as well. This function returns an Async<'a>, in this case being Async<WebResponse> where the result is web response from the WebRequest class. Underlying this whole piece is the use of continuations which takes in a function for the success and a function for handling failure. For a better understanding of how this works, I suggest that you look through the F# source code in the controls.fs file.
If you're curious, the Microsoft.FSharp.Control.CommonExtensions module contains a few extension methods that you can take advantage out of the box. Some of them include:
- File - Open (Text, Read, Write)
- Stream - Read and Write
- Socket - Accept, Receive and Send
- SqlCommand - Execute (Scalar, Reader, NonQuery)
I'll cover extension methods as part of my series on Object Oriented Programming in F# which is coming soon. Now that we have a basic understanding, let's move onto how to do this with a fresh web service.
Creating the Web Service
In this example, I'm going to extend a simple web service to be able to achieve asynchronous behavior during a computation expression. Let's define a simple MathService web service and then just add an Add function which adds two numbers together.
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class MathService : WebService
{
[WebMethod]
public int Add(int x, int y)
{
return x + y;
}
}
Consuming the Web Service
Now that we have defined the basic service. Use the WSDL tool to create a proxy to our web service and compile it to an assembly. Now that you've done that, we have a MathService.dll that we can now reference. Let's define now the extension method to our given web service to add the asynchronous behavior.
open MathServices
type MathServices.MathService with
member a.AddAsyncr(x, y) =
Async.BuildPrimitive(x, y, a.BeginAdd, a.EndAdd)
What I'm able to do here is add an AddAsyncr method to my MathService which binds the arguments of my function call as well as the BeginAdd and EndAdd methods. From the Async.BuildPrimitive, this builds me a Async<int> as a result of my addition that I did on the server. The current implementation can take multiple arguments, so it figures out my x and y parameters to be of type System.Int32.
Now to consume the service is rather simple. Let's first define the async computation expression to handle the addition:
async {
let service = new MathService()
let! result = service.AddAsyncr(x, y)
return result
}
I can then perform both a single operation of the call to the computation expression, or do them in parallel. Both are defined down below:
printfn "Results of single add: %d" singleAdd
let nums = [1..10]
let parallelAdd =
Async.Run(Async.Parallel
[for num in nums -> addNumbers num (num * 2)])
parallelAdd
|> Seq.iteri(fun x y -> printfn "Parallel Add at %d is %d" x y)
Then my results will look like this after iterating through each of the results:
Clean, concise and to the point. I like it!