Sunday, November 14, 2010

Side-by-Side Asynchronous Programming Example in F#, C#, and VB

I was pleased to hear the announcement at PDC2010 by Anders Hejlsberg that C# and VB will likely be following the lead of F# by including asynchronous programming support. As Don Syme states in his post on this topic "the proposed design of C# and VB asynchronous programming is pleasant and simple, and heavily inspired by the corresponding feature of F#". In this post I will provide a simple example in F#, C#, and VB. The provided example is based on an example from a post by Don entitled "Async and Parallel Design Patterns in F#: Parallelizing CPU and I/O Computations". This example is also very similar to those provided in a series of posts on this topic by Tomas Petricek. I strongly recommend reading Don's post as well as Tomas's series.

F#:
open System
open System.IO
open System.Net

let getHtml url printStrategy =
    async { let request =  WebRequest.Create(Uri url)
            use! response = request.AsyncGetResponse()
            use stream = response.GetResponseStream()
            use reader = new StreamReader(stream)
            let contents = reader.ReadToEnd()
            do printStrategy url contents
            return contents }
 
let sites = ["http://www.bing.com";
             "http://www.google.com";
             "http://www.yahoo.com";
             "http://msdn.microsoft.com/en-us/fsharp/default.aspx"]

let printStrategy url (contents:string) =  
    printfn "%s - HTML Length %d" url contents.Length

let sitesHtml = Async.Parallel [for site in sites -> getHtml site printStrategy]
                |> Async.RunSynchronously

do printfn "\r\nProcess Complete\r\nPress any key to continue"

do Console.ReadLine() |> ignore

C#:
using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using System.Linq;

namespace CSharpAsync
{
    class Program
    {
        static async Task<string> GetHtml(string url, 
            Action<string, string> printStrategy)
        {
            var request = WebRequest.Create(new Uri(url));
            using (var response = await request.GetResponseAsync())
            {
                using (var stream = response.GetResponseStream())
                {
                    using (var reader = new StreamReader(stream))
                    {
                        var contents = reader.ReadToEnd();
                        printStrategy.Invoke(url, contents);
                        return contents;
                    }
                }
            }        
        }

        static async Task ProcessSites(string[] sites, 
            Action<string, string> printStrategy)
        {
            var sitesHtml = 
                await TaskEx.WhenAll(
                    sites.Select(site => GetHtml(site, printStrategy)));
            Console.WriteLine("\r\nProcess Complete\r\nPress any key to continue");
        }

        static void Main(string[] args)
        {
            var sites = new[] { "http://www.bing.com",
                            "http://www.google.com",
                            "http://www.yahoo.com",
                            "http://msdn.microsoft.com/en-us/fsharp/default.aspx" };

            Action<string, string> printStrategy =
                (url, contents) =>
                    Console.WriteLine("{0} - HTML Length {1}", url, contents.Length);
            
            ProcessSites(sites, printStrategy).Wait();

            Console.ReadLine();
        }
    }
}

VB:
Imports System
Imports System.IO
Imports System.Net
Imports System.Threading.Tasks
Imports System.Linq

Module Module1

    Async Function GetHtml(ByVal url As String, ByVal printStrategy As Action(Of String, String)) As Task(Of String)
        Dim request = WebRequest.Create(New Uri(url))
        Using response = Await request.GetResponseAsync()
            Using stream = response.GetResponseStream()
                Using reader = New StreamReader(stream)
                    Dim contents = reader.ReadToEnd()
                    printStrategy.Invoke(url, contents)
                    Return contents
                End Using
            End Using
        End Using
    End Function

    Async Function ProcessSites(ByVal sites As String(), ByVal printStrategy As Action(Of String, String)) As Task
        Dim sitesHtml = _
            Await TaskEx.WhenAll(sites.Select(Function(site) GetHtml(site, printStrategy)))
        Console.WriteLine("{0}Process Complete{0}Press any key to continue", _
            Environment.NewLine)
    End Function

    Sub Main()
        Dim sites = New String() {"http://www.bing.com", _
                "http://www.google.com", _
                "http://www.yahoo.com", _
                "http://msdn.microsoft.com/en-us/fsharp/default.aspx"}

        Dim printStrategy As Action(Of String, String) = _
            Sub(url, contents) Console.WriteLine("{0} - HTML Length {1}", url, contents.Length)
        ProcessSites(sites, printStrategy).Wait()
        Console.ReadLine()
    End Sub

End Module


3 comments:

  1. Hey thanks for the code-snippets, it shows a nice side-by-side comparison of the different languages.

    Though your C# could be cleaner if you didn't use nested using blocks (i.e. remove braces from the top two using statements). Also I'm not sure why you created an extra method for 'ProcessSites()' instead of having it in-line like the F# version?

    Also I might be mistaken but I think you have an extra Wait on 'ProcessSites(sites, printStrategy).Wait();' as it looks like 'var sitesHtml = await TaskEx.WhenAll' already accomplishes this?

    - Demis

    ReplyDelete
  2. Thanks for your comments. You make a good point about the removal of the unnecessary braces from the top two using statements. The addition of the braces is an old habit of mine that stems from the coding standards of a company for which I use to work. I should definitely retire that habit.

    Tomas's post (http://tomasp.net/blog/csharp-fsharp-async-intro.aspx) goes into detail about the reasons for the extra method and the extra Wait call.

    ReplyDelete
  3. Hey thanks for the code-snippets, it shows a nice side-by-side comparison of the different languages.

    Though your C# could be cleaner if you didn't use nested using blocks (i.e. remove braces from the top two using statements). Also I'm not sure why you created an extra method for 'ProcessSites()' instead of having it in-line like the F# version?

    Also I might be mistaken but I think you have an extra Wait on 'ProcessSites(sites, printStrategy).Wait();' as it looks like 'var sitesHtml = await TaskEx.WhenAll' already accomplishes this?

    - Demis

    ReplyDelete