Monday, August 19, 2013

F# asynchrony and Task -> Task<unit> conversion

Quite a number of useful library XxxXxxAsync methods return just plain Task objects, when what it means is that the synchronous operation has a void type. This causes a bit of a snag when calling from F#, as it expects the subclass Task<T> in the natural way of things. And there are no good hits on the topic of Task -> Task<unit> conversion -- so this is a compendium of the techniques that do the job.

So we can treat the Task as an IAsyncResult and use

to translate via an Async<Boolean> to Async<unit>; or convert the Task to a Task<unit> via a continuation and AwaitTask that

The latter, based on the helper awaitPlainTask method at theburningmonk.com, separating out the bit before you pipe it into Async.AwaitTask (then

by composition); this Ignore function is analogous to Async.Ignore, and can be used in that sort of role for all Task and Task<T> inputs. For example, in conjunction with the fsharpx Task computation expression, where it fills in a painful gap when faced with plain Tasks.

2 comments :

Anonymous said...

Or extend async {} so that you can directly use both kinds of Task without Async.AwaitTask at all..

[]
module Async =
let inline AwaitUnitTask (task: Task) =
// rethrow exception from preceding task if it fauled
let continuation (t : Task) : unit =
match t.IsFaulted with
| true -> raise t.Exception
| _ -> ()
task.ContinueWith continuation |> Async.AwaitTask


type Microsoft.FSharp.Control.AsyncBuilder with
member x.Bind (t : Task<'T>, f : 'T -> Async<'R>) : Async<'R> = x.Bind (Async.AwaitTask t, f)

member x.Bind (t : Task, f : unit -> Async) : Async = x.Bind (Async.AwaitUnitTask t, f)

Steve Gilham said...

Yes does the whole job of making them part of the Async infrastructure -- and there's a similar transformation to extend the FSharpx.Task module, which is where I hit the problem of needing to make this conversion. In either case, it's all variations on the same core theme.

Exactly where to draw boundaries of small functions composed, or larger monolithic functions is one of these matters of style (if you only ever call a function in a composition, do you keep it as a separate concern or not?). For a blog post I'd rather give the smallest nuggets of functionality and let people weld them together as they see fit for their particular needs.