Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correctly awaiting in F# an async C# method with return type of Task<T>

I'd like to be able to consume a C# library from F#. Mostly this has been pretty straightforward. However, if I try to call a function that returns a Task<T> I am not able to get the returned value.

So, I have C# method with the following definition:

public async Task<TEvent> ReadEventAsync<TEvent>(string streamName, int position) where TEvent: class

And I am trying to consume this method from F# as follows:

let readEventFromEventStore<'a when 'a : not struct> (eventStore:IEventStoreRepository) (streamName:string) (position:int) = 
     async {
            return eventStore.ReadEventAsync(streamName, position) 
            |> Async.AwaitTask
            }

I partially apply this function with an instance of an IEventStoreRepository and the stream name I wish to retrieve the event from:

let readEvent = readEventFromEventStore eventStore streamName

Then, finally, I apply the remaining parameter:

let event = readEvent StreamPosition.Start

When I get the value of event it is a FSharpAsync<object> rather than the T from the Task<T> that I had expected.

What is the correct method in F# of calling an async method written in C# with a return type of Task<T> and accessing the value of T?

like image 599
David Brower Avatar asked Jan 19 '17 13:01

David Brower


People also ask

How do you use await in functions?

async and awaitInside an async function, you can use the await keyword before a call to a function that returns a promise. This makes the code wait at that point until the promise is settled, at which point the fulfilled value of the promise is treated as a return value, or the rejected value is thrown.

How do I use await in Python?

The keyword await passes function control back to the event loop. (It suspends the execution of the surrounding coroutine.) If Python encounters an await f() expression in the scope of g() , this is how await tells the event loop, “Suspend execution of g() until whatever I'm waiting on—the result of f() —is returned.

What await does in JavaScript?

The await operator is used to wait for a Promise and get its fulfillment value. It can only be used inside an async function or a JavaScript module.

Why We Use await in C#?

The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. When the asynchronous operation completes, the await operator returns the result of the operation, if any.


2 Answers

First of all, in your use case, there's no need for the async { } block. Async.AwaitTask returns an Async<'T>, so your async { } block is just unwrapping the Async object that you get and immediately re-wrapping it.

Now that we've gotten rid of the unnecessary async block, let's look at the type you've gotten, and the type you wanted to get. You got an Async<'a>, and you want an object of type 'a. Looking through the available Async functions, the one that has a type signature like Async<'a> -> 'a is Async.RunSynchronously. It takes two optional parameters, an int and a CancellationToken, but if you leave those out, you've got the function signature you're looking for. And sure enough, once you look at the docs it turns out that Async.RunSynchronously is the F# equivalent of C#'s await, which is what you want. sort of (but not exactly) like C#'s await. C#'s await is a statement you can use inside an async function, whereas F#'s Async.RunSynchronously takes an async object blocks the current thread until that async object has finished running. Which is precisely what you're looking for in this case.

let readEventFromEventStore<'a when 'a : not struct> (eventStore:IEventStoreRepository) (streamName:string) (position:int) =
    eventStore.ReadEventAsync(streamName, position) 
    |> Async.AwaitTask
    |> Async.RunSynchronously

That should get you what you're looking for. And note that technique of figuring out the function signature of the function you need, then looking for a function with that signature. It'll help a LOT in the future.

Update: Thank you Tarmil for pointing out my mistake in the comments: Async.RunSynchronously is not equivalent to C#'s await. It's pretty similar, but there are some important subtleties to be aware of since RunSynchronously blocks the current thread. (You don't want to call it in your GUI thread.)

Update 2: When you want to await an async result without blocking the current thread, it's usually part of a pattern that goes like this:

  1. Call some async operation
  2. Wait for its result
  3. Do something with that result

The best way to write that pattern is as follows:

let equivalentOfAwait () =
    async {
        let! result = someAsyncOperation()
        doSomethingWith result
    }

The above assumes that doSomethingWith returns unit, because you're calling it for its side effects. If instead it returns a value, you'd do:

let equivalentOfAwait () =
    async {
        let! result = someAsyncOperation()
        let value = someCalculationWith result
        return value
    }

Or, of course:

let equivalentOfAwait () =
    async {
        let! result = someAsyncOperation()
        return (someCalculationWith result)
    }

That assumes that someCalculationWith is NOT an async operation. If instead you need to chain together two async operations, where the second one uses the first one's result -- or even three or four async operations in a sequence of some kind -- then it would look like this:

let equivalentOfAwait () =
    async {
        let! result1 = someAsyncOperation()
        let! result2 = nextOperationWith result1
        let! result3 = penultimateOperationWith result2
        let! finalResult = finalOperationWith result3
        return finalResult
    }

Except that let! followed by return is exactly equivalent to return!, so that would be better written as:

let equivalentOfAwait () =
    async {
        let! result1 = someAsyncOperation()
        let! result2 = nextOperationWith result1
        let! result3 = penultimateOperationWith result2
        return! (finalOperationWith result3)
    }

All of these functions will produce an Async<'T>, where 'T will be the return type of the final function in the async block. To actually run those async blocks, you'd either do Async.RunSynchronously as already mentioned, or you could use one of the various Async.Start functions (Start, StartImmediate, StartAsTask, StartWithContinuations, and so on). The Async.StartImmediate example talks a little bit about the Async.SwitchToContext function as well, which may be something you'll want to read about. But I'm not familiar enough with SynchronizationContexts to tell you more than that.

like image 78
rmunn Avatar answered Oct 22 '22 23:10

rmunn


An alternative to using the async computation expression for this situation (F# calling C# Task-based XxxAsync method) is to use the task computation expression from:

https://github.com/rspeele/TaskBuilder.fs

The Giraffe F# web framework uses task for more or less the same reason:

https://github.com/giraffe-fsharp/Giraffe/blob/develop/DOCUMENTATION.md#tasks

like image 2
Bellarmine Head Avatar answered Oct 22 '22 23:10

Bellarmine Head