user2363676
user2363676

Reputation: 341

How to use awaitable inside async?

I'm fairly new to F# and as practice I'm currently writing a module that has a lot of interop with C# libraries.

When library code returns me Task, it's not a problem - I already found a package that enables let! with tasks.

Right now I'm using cliwrap and its ExecuteAsync() method returns not a Task, but some other awaitable type(you can await it in C# and it has GetAwaiter() method).

So far I came up with the following code to use it(please ignore usage of test and (), its temprorary for debugging):

 let testLaunchDelayed = async {
                        let cmd = Cli.Wrap(@"C:\Launcher\Launcher.exe").WithArguments("--TestName " + testName)
                        let test = cmd.ExecuteAsync().GetAwaiter().GetResult()
                        ()
                    }
                    Async.StartImmediate testLaunchDelayed

It seems to work, but using .GetAwaiter().GetResult() feels wrong. Is there a better way ?

Update Tried using taskbuilder, ended up with this:

async {
      let cmd = Cli.Wrap(@"C:\Test\git\tests\func\nunit\AllBin\publish_win10-x64\CommonTestLauncher.exe").WithArguments("--TestName " + testName);
      let! cmdTask = task{return! cmd.ExecuteAsync()}
      ()
}

Upvotes: 1

Views: 223

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243051

The issue with using GetResult is that this is a synchronous blocking call. Ideally, you want to use the OnCompleted event to get notified when the operation completes and, only then, access the result. You can encapsulate this into a helper awaitAwaiter, which is much like the Async.AwaitTask operation:

let awaitAwaiter (a:Runtime.CompilerServices.TaskAwaiter<_>) = 
  Async.FromContinuations(fun (cont, econt, ccont) ->
    a.OnCompleted(fun () -> 
      let res = 
        try Choice1Of3 (a.GetResult())
        with 
        | :? System.Threading.Tasks.TaskCanceledException as ce -> Choice2Of3 ce
        | e -> Choice3Of3 e 
      match res with 
      | Choice1Of3 res -> cont res
      | Choice2Of3 ce -> ccont (OperationCanceledException("Task has been cancelled", ce))
      | Choice3Of3 e -> econt e ) )

This uses FromContinuations to create a new asynchronous computation that uses OnCompleted to get notified when the work is done and then triggers appropriate continuation (handling errors - hopefully correctly, but I have not tested this!)

The use is the same as with AwaitTask:

let testLaunchDelayed testName = async {
  let cmd = Cli.Wrap(@"C:\Launcher\Launcher.exe").WithArguments("--TestName " + testName)
  let test = cmd.ExecuteAsync().GetAwaiter() |> awaitAwaiter
  () }

Upvotes: 3

Related Questions