Functional C# - Implementing Async Computations in C#
Update: Removed Internal Constructor constraint on AsyncBuilder.
As I covered earlier in my post Functional .NET - LINQ or Language Integrated Monads, I talked about using asynchronous computation expressions (monads) from C# 3.0. Brian McNamara, of the F# team, posted back in May about using them from C#. But since then, things have changed slightly. Before, I showed a basic example of how to utilize the F# libraries from C#, but let's go deep under the covers to see how this actually works.
Getting Started
In order to make use of the F# libraries in our C# library, we need to add references to them. We need the following items:
- FSharp.Core.dll
- FSharp.PowerPack.dll
- FSharp.PowerPack.Linq.dll
And then I need to open the namespaces in order to take advantage of F#:
using Microsoft.FSharp.Control;
using Microsoft.FSharp.Core;
Referencing F# Classes
As part of process of creating the asynchronous monad builders in C#, we need to create an instance of the F# class AsyncBuilder in the Microsoft.FSharp.Control namespace. We can get a reference to this from the Pervasives class, through the async property. This is a static reference which is available to all.
{
public static AsyncBuilder async = Pervasives.async;
...
Now that we have this, we have to realize that F# doesn't use the standard .NET delegates for functions. Let's walk through some ways of converting back and forth.
Converting Functions
As we've noted before, F# does not use the standard Func and Action delegates that are commonly used in C# and VB.NET. Instead, the functions in F# use the FastFunc class. This allows for the F# compiler to better optimize the closures, especially due to the fact that these closures are quite commonly used. Another point of difference is that there is no distinction between Func and Action delegates, and instead, for functions that return no value have the return type of Unit. This is the optimal way of handling this, due to the fact that Void is not treated as a real type, which you've heard me complain about in the past.
In order to convert from the Func delegate to the FastFunc, we use the FuncConvertExtensions class in the FSharp.PowerPack.Linq.dll assembly. Then we can create extension methods on our Func delegates to expose the conversion methods. The conversion methods should look like this.
{
public static FastFunc<Tuple<A, B>, C> ToTupledFastFunc<A, B, C>
(this Func<A, B, C> f)
{
return FuncConvertExtensions.ToTupledFastFunc(f);
}
public static FastFunc<Tuple<A, B, C>, D> ToTupledFastFunc<A, B, C, D>
(this Func<A, B, C, D> f)
{
return FuncConvertExtensions.ToTupledFastFunc(f);
}
public static FastFunc<A, B> ToFastFunc<A, B>
(this Func<A, B> f)
{
return FuncConvertExtensions.ToFastFunc(f);
}
Now that the conversions have been put in place, we can turn our attention to creating the extension methods required for LINQ expressions.
Adding LINQ Extension Methods
In order for LINQ to bind and return data, the SelectMany and Select methods must be implemented. We need to implement these methods to return an Async<T> class for binding and returning purposes. As part of the implementation, we need to ensure our Func delegates are converted to the proper FastFunc types.
{
public static Async<B> Select<A, B>(
this Async<A> x, Func<A, B> selector)
{
return async.Bind(x,
ToFastFunc<A, Async<B>>((r) => async.Return(selector(r))));
}
public static Async<V> SelectMany<T, U, V>(
this Async<T> p, Func<T, Async<U>> selector, Func<T, U, V> projector)
{
return async.Bind(p, ToFastFunc<T, Async<V>>(r1 =>
async.Bind(selector(r1), ToFastFunc<U, Async<V>>(r2 =>
async.Return(projector(r1, r2))))));
}
Once the LINQ methods are added, we can turn our attention on how we might actually implement the asynchronous behavior with our given classes.
Adding Extensions Building Primitives
In order for your classes to use the asynchronous behavior, we need to expose ways of building primitives so that we can enlist those methods that expose the Beginxxx and Endxxx signature which includes the IAsyncResult. Also, in order to support method calls that do not, we have the ability within the asynchronous computation expressions to unblock via a new thread.
First, we need to define the method which allows us to do non-blocking calls on methods that do not support the Begin/End pattern. In order for this to happen, we need to call an internal method to the FileExtensions class in the FSharp.PowerPack.dll assembly called UnblockViaNewThread. Due to the fact that it is a generic method, we have to do special binding using reflection. Once this has been created, we can now enlist functions that follow the Func<Res> signature.
Second, we need to define methods to build primitives to allow for methods that follow the Begin/End pattern using the IAsyncResult and AsyncCallback delegate. Overloads are necessary due to the fact that methods may have return types or not. Examples of this pattern are Stream.BeginRead, Stream.BeginWrite and so on.
Lastly, we need the ability to start the asynchronous computation expression. This must be the first statement in any asynchronous computation expression that we write. Let's look at how the code is implemented.
{
public static Async<T> UnblockViaNewThread<T>(FastFunc<Unit, T> f)
{
var type = typeof(FileExtensions);
var methodInfo = type.GetMethod(
"UnblockViaNewThread", BindingFlags.NonPublic | BindingFlags.Static);
var genericArguments = new[] { typeof(T) };
var genericMethodInfo = methodInfo.MakeGenericMethod(genericArguments);
return genericMethodInfo.Invoke(null, new[] { f }) as Async<T>;
}
public static Async<Unit> BuildVoidPrimitive(
Func<AsyncCallback, object, IAsyncResult> begin,
Action<IAsyncResult> end)
{
return Async.BuildPrimitive(
begin.ToTupledFastFunc(), FuncConvert.ToFastFunc(end));
}
public static Async<R> BuildPrimitive<R>(
Func<AsyncCallback, object, IAsyncResult> begin,
Func<IAsyncResult, R> end)
{
return Async.BuildPrimitive(
begin.ToTupledFastFunc(), end.ToFastFunc());
}
public static Async<R> BuildPrimitive<Arg, R>(
Arg a,
Func<Arg, AsyncCallback, object, IAsyncResult> begin,
Func<IAsyncResult, R> end)
{
return Async.BuildPrimitive(
a, begin.ToTupledFastFunc(), end.ToFastFunc());
}
public static Async<int> StartWorkflow = async.Return(0);
Now that we have the ability to add asynchronous behavior to our classes, let's implement some extension methods that encompass the behavior.
Extending Types With The Begin/End Pattern
For methods that have the standard Begin/End methods can use the BuildPrimitive methods that we defined above. As our first example, let's implement an asynchronous WebRequest.GetResponse. Normally in our .NET code, we have to implement the Begin/End ourselves. Instead, we pass the methods to the BuildPrimitive method to bind.
{
public static Async<WebResponse> AsyncGetResponse
(this WebRequest request)
{
return AsyncExtensions.BuildPrimitive<WebResponse>(
request.BeginGetResponse, request.EndGetResponse);
}
}
Any method signature that follows this pattern should be able to partake. Now what about those methods that do not follow the Begin/End pattern. What can we do about those?
Extending Types with Non-Blocking Threads
As I mentioned previously, I want the ability to perform asynchronous operations on those methods that do not follow the Begin/End pattern. In order to do that, I must use the UnblockViaNewThread method that was defined above. For example, we could expose the ability to open files asynchronously as either readers or streams. Let's define some methods to open files asynchronously.
{
public static Async<FileStream> AsyncOpen(
string path, FileMode mode, FileAccess access, FileShare share)
{
Func<FileStream> f = () =>
File.Open(path, mode, access, share);
return AsyncExtensions.UnblockViaNewThread(FuncConvertExtensions.ToFastFunc(f));
}
public static Async<FileStream> AsyncOpenRead(string path)
{
Func<FileStream> f = () => File.OpenRead(path);
return AsyncExtensions.UnblockViaNewThread(FuncConvertExtensions.ToFastFunc(f));
}
public static Async<StreamReader> AsyncOpenText(string path)
{
Func<StreamReader> f = () => File.OpenText(path);
return AsyncExtensions.UnblockViaNewThread(FuncConvertExtensions.ToFastFunc(f));
}
}
This pattern would also apply to such things as WebRequest.Create and so on. Now, let's bring it all together.
Bringing It All Together
Now that we have defined the extension methods and other asynchronous methods, let's tie it all back together. First, let's look at a quick example of an asynchronous computation expression that will retrieve the number of hyperlinks from a web page.open System.IO
open System.Net
open System.Text.RegularExpressions
let get_links html =
let linkRegex = new Regex(
@"<a\s{1}href=\""(?<url>.*?)\""(\s?target=\""" +
@"(?<target>_(blank|new|parent|self|top))\"")?" +
@"(\s?class=\""(?<class>.*?)\"")?(\s?style=\""" +
@"(?<style>.*?)\"")?>(?<title>.*?)</a>");
linkRegex.Matches(html) |> Seq.cast<Match>
let links_async (requestUriString:string) =
async {
let request = WebRequest.Create(requestUriString)
let! response = request.AsyncGetResponse()
let reader = new StreamReader(response.GetResponseStream())
let! html = reader.AsyncReadToEnd()
let links = get_links html
return requestUriString, links |> Seq.length
}
let sites = ["http://live.com/";
"http://www.google.com/";
"http://codebetter.com/"]
let results = Async.Run(
Async.Parallel [for site in sites -> links_async site])
results |> Seq.iter(
fun result -> printfn "Site %s has %d links" (fst result) (snd result))
What the above code does for me is allows me to count the number of hyperlinks, given a URL, in parallel. This is a pretty simplistic example, yet works quite well for this demonstration. Now, using our C# implementation, let's take the above code and get it to work in C#.
{
var linkRegex = new Regex(
@"<a\s{1}href=\""(?<url>.*?)\""(\s?target=\""" +
@"(?<target>_(blank|new|parent|self|top))\"")?" +
@"(\s?class=\""(?<class>.*?)\"")?(\s?style=\""" +
@"(?<style>.*?)\"")?>(?<title>.*?)</a>");
return linkRegex.Matches(html).Cast<Match>();
};
Func<string, Async<Tuple<string, int>>> links_async = requestUriString =>
from _ in AsyncExtensions.StartWorkflow
let request = WebRequest.Create(requestUriString)
from response in request.AsyncGetResponse()
let reader = new StreamReader(response.GetResponseStream())
from html in reader.AsyncReadToEnd()
let links = get_links(html)
select new Tuple<string, int>(requestUriString, links.Count());
var sites = new[]
{
"http://live.com/",
"http://www.google.com/",
"http://codebetter.com/"
};
var results = Async.Run(
Async.Parallel(from site in sites select links_async(site)),
Option<int>.None,
Option<bool>.None);
foreach (var result in results)
Console.WriteLine("Site {0} has {1} links", result.Item1, result.Item2);
For each of the above, we get the following result:
Well, that works exactly according to plan. But, using this technique, are there any downsides?
Downsides?
The asynchronous code I wrote above is pretty simple and naive. Counting links in an HTML page is a pretty simple example with some asynchronous calls. But, this breaks down easily if it gets more complex than this. There are constructs that can be done in the asynchronous computation expressions in F# that cannot be done through LINQ. For example, there are several things that cannot be done through LINQ expressions such as the following:
- if/then/else
- while loops
- try/catch/finally
- methods that return void
These above constructs don't have a 1 to 1 mapping with LINQ. As a result, our code may have to look significantly different than the F# code that we'd write. That of course eliminates some of the savings we may have had with our LINQ implementation.
open Microsoft.FSharp.Control
let agent = MailboxProcessor.Start(fun inbox ->
let rec loop n =
async {
do printfn "n = %d" n
let! message = inbox.Receive()
if n % 2 = 0 then
do printfn "message %d is an even number" message
else
do printfn "message %d is an odd number" message
return! loop(n + message)
}
loop 0)
As you see, when combined with such things as the MailboxProcessor, the asynchronous computation expressions become a bit more powerful. Unfortunately, LINQ doesn't support these features of conditionals, using statements and so on. The alternative being, using the awkward syntax with converting between FastFunc and Func delegates, which negates any savings we may have had.
Wrapping It Up
As I've shown in the past posts, LINQ expressions could be used to your advantage to do more than just standard query operations. Instead, if we start to think of them as very powerful monadic constructs, we can then think of better uses for them. Although the LINQ expressions are limited in what they can achieve through the asynchronous computation expressions, it still gets you quite far. I hope this exploration gets you to understand languages such as Haskell and how monads are useful. For further information, you should check out "What is a monad, why should I use it and when it is appropriate" on Lambda the Ultimate.
The code from this post is available, as always from the Functional C# Library on MSDN Code Gallery.