Xamarin F# 到 C# 异步调用:正确的现代方式

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

我正在尝试从 Xamarin 中的 F# 代码同步调用 C# 异步函数。原始 C# 代码如下所示(这是 Plugin.BLE 目标设备服务发现):

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

如果我使用推荐的 F# 原语

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

以下代码:

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

无法编译,因为“字段、构造函数或成员 'GetCharacteristicAsync' 未定义。”。似乎 F# 在 AwaitPlainTask 之后无法正确确定或转换类型。

如果我尝试以常规方式进行操作,例如:

async {
         let serviceTask = d.GetServiceAsync(serviceGuid)

         serviceTask.Wait()

         let tsk = serviceTask.Result.ToString()

serviceTask.Result 始终为空。

的结果相同(null)
let! service = d.GetServiceAsync(serviceGuid) |> Async.AwaitTask

也是。 C# 代码正在运行,所以基本上问题在于如何从 F# 调用它。我想我在这里误解了什么,处理这种结构的正确方法是什么?

P.S.: 这不是 问题的重复,因为它可能已经过时并且不再有效(至少在 Xamarin 上)。

您在此处使用的函数 AwaitPlainTask,看起来是处理 Task 的便利函数。但是,在 F# async 计算表达式中有一种处理 Task<T> 的标准方法。您应该使用 Async.AwaitTasklet!,如下所示:

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
...

否则你所做的是 C# 中将 Task<T> 转换为 Task 并尝试等待它的等价物:

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

这不会在 C# 中编译,但是在 F# 中我们有 unit 类型,这实际上非常方便,因为您不需要类型的扩散和泛型代码的重载。

还值得注意的是,您可能可以删除 AwaitPlainTask,因为它现在是自 F# 4.0 以来 Async.AwaitTask 中构建的重载,例如:

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

我看了Plugin.BLE 好像waitng GetServiceAsync 可以return null 就像你说的,这是库的设计。

为了处理这个问题,我们可以将结果包装在 Option 类型中,并将其用作之后访问它的安全方式。例如:

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 ->   ()