Xamarin F# to C# async call: correct modern way

133 views Asked by At

I'm trying to call C# Async function synchronously from F# code in Xamarin. The original C# code looks like (this is the Plugin.BLE target device service discovery):

            {
                var service = await _connectedDevice.GetServiceAsync(serviceGuid);
                if (service != null)
                {
                    var characteristic = await service.GetCharacteristicAsync(Guid.Parse(characteristicGuid);
                    if (characteristic != null)
                    {

If I'm using recommended F# primitive

module Async =
    let inline AwaitPlainTask (task: Task) = 
       task.ContinueWith(fun t -> ())
       |> Async.AwaitTask 

The following code:

async {
         let! service = Async.AwaitPlainTask <|d.GetServiceAsync(serviceGuid)
         let! characteristic = Async.AwaitPlainTask <|service.GetCharacteristicAsync(characteristicGuid)
...

could not be compiled because 'The field, constructor or member 'GetCharacteristicAsync' is not defined.'. Seems F# could not correctly determine or convert type after AwaitPlainTask.

If I'm trying to do it in the regular way, e.g.:

async {
         let serviceTask = d.GetServiceAsync(serviceGuid)

         serviceTask.Wait()

         let tsk = serviceTask.Result.ToString()

the serviceTask.Result is always null. The same result (null) happens for

let! service = d.GetServiceAsync(serviceGuid) |> Async.AwaitTask

too. C# code is working, so basically the problem is with how it's called from F#. I think I misunderstood something here, what is the correct way to deal with this type of constructions?

P.S.: It's not a duplicate of this question, because that probably is outdated and no longer works (at least on Xamarin).

1

There are 1 answers

4
Stuart On BEST ANSWER

The function you are using here AwaitPlainTask, looks like it's a convenience function to deal with Task. However there is a standard way of dealing with Task<T> in F# async computation expressions. You should be using Async.AwaitTask, along with let!, like this:

async {
    // Removing back-pipes as they are often more confusing that useful
    let! service = d.GetServiceAsync(serviceGuid) |> Async.AwaitTask
    let! characteristic = service.GetCharacteristicAsync(characteristicGuid) |> Async.AwaitTask
...

What you are doing otherwise is the equivalent in C# of casting Task<T> to Task and trying to await it:

var result = await ((Task)d.GetServiceAsync(serviceGuid))

which wouldn't compile in C#, however in F# we have the unit type, which actually is really handy as you don't a proliferation of types and overloads of generic code.

It's also worth noting that you can probably delete AwaitPlainTask as it now is an overload of the build in Async.AwaitTask since F# 4.0, for example:

let waitOneSecond () = async {
    // Use do! not let! when you aren't using the result
    do! Task.Delay(1000) |> Async.AwaitTask
}

I've had a look at Plugin.BLE and it looks like awaitng GetServiceAsync can return null as you say, it is the design of the library.

To handle this, we could wrap the result in an Option type and use that as a safe way to access it after. For example:

let! service = d.GetServiceAsync(serviceGuid) |> Async.AwaitTask
let service = service |> Option.ofObj
match service with
| Some s -> let! characteristic = s.GetCharacteristicAsync(characteristicGuid) |> Async.AwaitTask
            ()
| None ->   ()