Working in enterprise level back end software often times I need to get
information from many different sources. When you have enough of these sources
the time it takes to get all the information can really add up. That’s when F#
async
work flow really comes in handy. Except, there is a gotcha.
Let’s say we have two sources we need to fetch data from:
let myData () = async {
let! a = myData1 ()
let! b = myData2 ()
let resultA = a
let resultB = b
// ... do stuff
// return result
}
So, when you look at that it looks like it should fetch the two data sources at the same time. Well, it doesn’t. It does it one at a time, serially. It just doesn’t block the thread while it is doing the fetching for each asynchronous call.
This is what you need to do to do it in parallel:
let myData () = async {
let! a = Async.StartChild <| myData1 ()
let! b = Async.StartChild <| myData2 ()
let! resultA = a
let! resultB = b
// ... do stuff
// return result
}
Now it is doing it at the same time! To me this feels a bit unintuitive. Enter
JavaScript-style promises. Promise objects point to data and don’t execute
until you are ready to execute them as an async
function does in F#.
let myData () =
Q
.all([myData1(), myData2()]) // Call two promises at the same time.
.spread((resultA, resultB) => {/* Do stuff here. */})
Could we do something like that in F#? We can mimic the process in F# by having multiple functions depending on how many asynchronous processes you would like to run at a time.
Let’s say you would like to have two asynchronous functions run at once.
module Async =
let Spread2 a b =
async {
let! a = Async.StartChild a
let! b = Async.StartChild b
let! result1 = a
let! result2 = b
return result1, result2
}
|> Async.RunSynchronously
Which would flow like so:
let myData () =
Async.Spread2 (myData1 ()) (myData2 ())
|> fun (resultA, resultB) -> // Do stuff here.
Pretty close to what the JavaScript promise looked like. In my opinion this
work flow is much more intuitive and cleaner. It has a nicer flow than the
async
computation-style programming by not having to deal with the boiler
plate code. I could see having a few parallel functions all the way up to perhaps
Async.Spread6
. It would be very rare for me to ever need to call anything with
more external calls. Usually, I’m only calling two or three web services with the
rare five or six.
A variation on the function above would be to add mapping functions to your parallel function. I’m not sure if that would be useful in practice, but if it is I’ll be adding it to mine.
module Async =
let Spread2With (a, f1) (b, f2) =
async {
let! a = Async.StartChild a
let! b = Async.StartChild b
let! result1 = a
let! result2 = b
return (f1 result1), (f2 result2)
}
|> Async.RunSynchronously
I also debate whether it is smart to add the Async.RunSynchronously
at the end. If I didn’t have it I could use Spread2
to create Spread4
etc. If I ever needed more than six it might be worth it. But for now I haven’t run into that problem since I started using this pattern.
Inspiration for this post:
http://fsharpforfunandprofit.com/posts/recipe-part2/#comment-1508742658
http://stackoverflow.com/a/15829290/632495