As Don Syme points out in his piece on asynchronous workflows, they are not about getting the whole of concurrency right but rather about getting asynchronous I/O.  I think that to fully appreciate the beauty of asynchronous workflows one must understand the ugliness of what it’s like to do asynchronous I/O without them. I think this may be true for many areas of software development,  hell even good old C looks great if you’ve been forced to spend some time assembler, and I guess Joel and his law of “leaky abstractions” is saying something along a similar lines. So I guess the aim of this article is to make you think that workflows are great by show you the “assembler” of asynchronous programming, or at least the asynchronous I/O part of it.

So we when we create a work flow as shown below we are making use of the BeginGetAtoms and EndGetAtoms methods that are already offered by the web service proxy class. The different is that these methods have been wrapped into a single method call “GetAtomsAsyncr”, which handles the calls to these methods.

let atomsWorkFlowSimple =

    async { let pt = new PeriodicTableWS.periodictable()

            let! atoms = pt.GetAtomsAsyncr()

            do System.Console.WriteLine atoms }

 

 

Async.Spawn (atomsWorkFlowSimple)

 

When attempting to rewrite this without asynchronous workflows you’d need to write something like:

let ws = new PeriodicTableWS.periodictable()

let getResult ar =

    let res = ws.EndGetAtoms(ar)

    System.Console.WriteLine(res)

 

ws.BeginGetAtoms((fun ar -> getResult ar), null)

 

Here we use the BeginGetAtoms method to register a call-back, when the I/O is complete the call back is called and we can retrieve the result. Okay so the listings are approximately the same length, but this listing to me the first listing looks much more natural than the second. The asynchronous workflow also has the advantage that it’s easy to rewrite it retrieve the results of the method call:

let atomsWorkFlow =

    async { let pt = new PeriodicTableWS.periodictable()

            let! atoms = pt.GetAtomsAsyncr()

            return atoms }

 

let atoms = Async.Run (atomsWorkFlow)

 

Whereas for the call-back case, the results of the method call are pretty much trapped within the call-back. Sure we could retrieve it using some mutable state, but that would raise lots of locking issues making the whole thing very awkward. I think the advantages of the asynchronous workflows really begin to try and grow the example, the disadvantage of the call-backs really begin to show themselves, here we translate the example from the previous blog post:

let ws = new PeriodicTableWS.periodictable()

let getWeightResult ar =

    let weight = ws.EndGetAtomicWeight(ar)

    let atom = ar.AsyncState :?> string

    let weight = selectSingleNode weight "/NewDataSet/Table/AtomicWeight"

    wl ( threadid() + atom + ": " + weight)

 

let getAtomsResult ar =

    let atoms = ws.EndGetAtoms(ar)

    let atoms = getNodeContentsList atoms "/NewDataSet/Table/ElementName"

    wl ( threadid() + "Got " + atoms.Length.ToString() + " Elements")

    for atom in atoms do

        wl ( threadid() + "Get Data For: " + atom)

        ws.BeginGetAtomicWeight(atom, (fun ar -> getWeightResult ar), atom) |> ignore

 

let main() =

    wl ( threadid() + "Get Element Data List")

    ws.BeginGetAtoms((fun ar -> getAtomsResult ar), null) |> ignore

 

main()

 

We see here that the using the second call-back, getWeightResult, means that the name of the original atom is not readily available like in the asynchronous workflow version, we have to pass in the state object of the call back and then retrieve it using a cast. There’s also a subtle disadvantage, if we take a look at the output from the call back example:

[.NET Thread 7]Get Data For: Actinium

[.NET Thread 7]Get Data For: Aluminium

[.NET Thread 7]Get Data For: Americium

[.NET Thread 7]Get Data For: Antimony

[.NET Thread 7]Get Data For: Argon

[.NET Thread 7]Get Data For: Arsenic

[.NET Thread 7]Get Data For: Astatine

[.NET Thread 7]Get Data For: Barium

[.NET Thread 7]Get Data For: Berkelium

[.NET Thread 7]Get Data For: Beryllium

 

we see that all the request are executed on the same thread, unlike the asynchronous workflow version. This is because in the workflow we use Async.Spawn to set off the second workflow which registers the method with the thread pool which means the request can be set off on different thread if they are available to run them. To get this effect in the call-back version this would mean adding extra calls to ThreadPool.QueueUserWorkItem.

This has been a quick tour showing the advantages that asynchronous workflows bring over the using the BeginXXX and EndXXX methods and call-backs for asynchronous I/O. In the next part of the series we’ll take a look at using asynchronous workflows another way – to perform Erlang style message passing.

Feedback:

Feedback was imported from my only blog engine, it's no longer possible to post feedback here.

Robert on "Understanding how Asynchronous Workflows Work" - Don Syme's WebLog on F# and Other Research Projects

Robert Pickering has just posted a nice blog entry showing how programs look if you don't have asynchronous

re: Concurrency in F# - Understanding how Asynchronous Workflows Work - Patrick Brombach

Hi Robert,

For your information, the link on your e-mail address don't work.

I also live in Paris. I there a F# comunity here? Are they organising meetings?

Thanks

Patrick

re: Concurrency in F# - Understanding how Asynchronous Workflows Work - Robert Pickering

Hi Patrick,

I know a couple of other F# users in Paris, but we have no organised meetings yet. Drop me a line at robert at strangelights.com if you would like to meet up for a beer and talk F#.

Cheers,
Rob