38

I'm trying to consume a C# library in F#. The library makes heavy use of async/await. I want to use within an async { ... } workflow in F#.

I see we can Async.AwaitTask on async C# methods returning Task<T>, but what about those returning plain Task?

Perhaps, is there a helper to convert these to Async<unit> or to convert Task to Task<unit> so it will work with Async.AwaitTask?

AshleyF
  • 3,682
  • 1
  • 25
  • 23

4 Answers4

48

You can use ContinueWith:

let awaitTask (t: Task) = t.ContinueWith (fun t -> ()) |> Async.AwaitTask

Or AwaitIAsyncResult with infinite timeout:

let awaitTask (t: Task) = t |> Async.AwaitIAsyncResult |> Async.Ignore
zeuxcg
  • 9,216
  • 1
  • 26
  • 33
  • 10
    Ah, I see. Of course, Task is an IAsyncResult after all. Shortened to `let awaitTask = Async.AwaitIAsyncResult >> Async.Ignore` Thanks much! – AshleyF Nov 06 '11 at 00:50
  • 1
    ContinueWith is not idiomatic F#, plus it's dangerous because it could throw TaskCanceledException – knocte Jan 17 '19 at 10:19
18

Update:

The FSharp.Core library for F# 4.0 now includes an Async.AwaitTask overload that accepts a plain Task. If you're using F# 4.0 then you should use this core function instead of the code below.


Original answer:

If your task could throw an exception then you probably also want to check for this. e.g.

let awaitTask (task : Task) =
    async {
        do! task |> Async.AwaitIAsyncResult |> Async.Ignore
        if task.IsFaulted then raise task.Exception
        return ()
    }
Dave
  • 4,420
  • 1
  • 34
  • 39
  • `raise task.Exception` will just raise the AggregateException; it's probably better to access its `InnerExceptions` collection, and raise the first one (so long as the collection isn't empty) – Bellarmine Head Mar 31 '16 at 15:19
15

Update:

The FSharp.Core library for F# 4.0 now includes an Async.AwaitTask overload that accepts a plain Task. If you're using F# 4.0 then you should use this core function instead of the code below.

Original answer:

I really liked Ashley's suggestion using function composition. Additionally, you can extend the Async module like this:

module Async =
    let AwaitTaskVoid : (Task -> Async<unit>) =
        Async.AwaitIAsyncResult >> Async.Ignore

Then it appears in Intellisense along with Async.AwaitTask. It can be used like this:

do! Task.Delay delay |> Async.AwaitTaskVoid

Any suggestions for a better name?

Wallace Kelly
  • 15,565
  • 8
  • 50
  • 71
1

To properly propagate both exceptions and cancellation properly, I think you need something like this (partially based on deleted answer by Tomáš Petříček):

module Async =
    let AwaitVoidTask (task : Task) : Async<unit> =
        Async.FromContinuations(fun (cont, econt, ccont) ->
            task.ContinueWith(fun task ->
                if task.IsFaulted then econt task.Exception
                elif task.IsCanceled then ccont (OperationCanceledException())
                else cont ()) |> ignore)
svick
  • 236,525
  • 50
  • 385
  • 514