views:

922

answers:

2

I have some stuff written in c# that executes concurrent code, making heavy use of the Task Parallel Library (Task and Future continuation chains).

I'm now porting this to F# and am trying to figure out the pros and cons of using F# Async workflows vs. the constructs in the TPL. I'm leaning towards TPL, but I think it could be done either way.

Does anyone have tips and wisdom about writing concurrent programs in F# to share?

+12  A: 

The name pretty much sums up the difference: asynchronous programming vs. parallel programming. But in F# you can mix and match.

F# Asynchronous Workflows

F# async workflows are helpful when you want to have code execute asynchronously, that is starting a task and not waiting around for the final result. The most common usage of this is IO operations. Having your thread sit there in an idle loop waiting for your hard disk to finish writing wastes resources.

If you began the write operation asynchronously you can suspend the thread and have it woken up later by a hardware interrupt.

Task Parallel Library

The Task Parallel Library in .NET 4.0 abstracts the notion of a task - such as decoding an MP3, or reading some results from a database. In these situations you actually want the result of the computation and at some point in time later are waiting for the operation's result. (By accessing the .Result property.)

You can easily mix and match these concepts. Such as doing all of your IO operations in a TPL Task object. To the programmer you have abstracted the need to 'deal with' that extra thread, but under the covers you're wasting resources.

Like wise you can create a series of F# async workflows and run them in parallel (Async.Parallel) but then you need to wait for the final result (Async.RunSynchronously). This frees you from needing to explicitly start all the tasks, but really you are just performing the computation in parallel.

In my experience I find that the TPL is more useful because usually I want to execute N operations in parallel. However, F# async workflows are ideal when there is something that is going on 'behind the scenes' such as a Reactive Agent or Mailbox type thing. (You send something a message, it processes it and sends it back.)

Hope that helps.

Chris Smith
I agree with Chris. If your threads are going to be doing work all the time you might as well stick with TPL. Only when you're dealing with IO or more generally operations that return IAsyncResult, would I recommend using async workflows. The benefit of async workflows is that the threads get returned back to the pool until the async operation completes.
donovan
A: 

In 4.0 I would say:

  • If your function is sequential, use Async workflows. They simply read better.
  • Use the TPL for everything else.

It's also possible to mix and match. They've added support for running a workflow as a task and creating tasks that follow the async Begin/End pattern using TaskFactory.FromAsync, the TPL equivalent of Async.FromBeginEnd or Async.BuildPrimitive.

let func() =
    let file = File.OpenRead("foo")
    let buffer = Array.zeroCreate 1024
    let task1 = Task.Factory.FromAsync(file.BeginRead(buffer, 0, buffer.Length, null, null), file.EndRead)
    task1.Start()

    let task2 = Async.StartAsTask(file.AsyncRead(1024))
    printfn "%d" task2.Result.Length

It's also worth noting that both the Async Workflows runtime and the TPL are going to create an extra kernel primitive (an Event) and use WaitForMultipleObjects to track I/O completion, rather than using completion ports and callbacks. This is undesirable in some applications.

Nathan Howell
Nathan, why is it undesirable? Is it something about a possibility to make the GUI unresponsive?
Bruno Reis
It's undesirable in high throughput server applications. For most applications it won't matter, if you're trying to service thousands of requests per second the little things add up. The NT kernel was designed around completion ports (event queues) which map naturally to the callback pattern. The F# Async monad uses signals (via something like RegisterWaitForSingleObject) to allow cancelation and unfortunately this has some overhead. Again, not something to worry about unless you're designing for very high throughput.
Nathan Howell
@nathan: Is there any documentation you can point to that explains the creation of this kernel primitive. I assume the FromAsync method to hook up the completion via the callback that can be passed on to the begin method. It will be interesting to see what has prevented the library from doing that, and how we can build a higher order API for the same.
Charles Prakash Dasari
It looks like they've fixed F# v1.9.6.16 (2010 Beta 2) to use callbacks. The broken behavior was pretty easy to spot in previous builds, I used v1.9.6.16. Take look at Async.BuildPrimitive and Async.WaitIAsyncResult in FSharp.Core\control.fs. I also verified it in windbg, definitely working properly in Beta 2 :)
Nathan Howell
Doh, the fixed version is v1.9.7.8 (2010 Beta 2) not v1.9.6.16
Nathan Howell