如何使用回调从 C# 方法创建 F# 异步?



假设我有一些接受回调的 C# 代码:

void DoSomething(Action<string> callback);

现在,我想在 F# 中使用它,但将其包装在async中。我该怎么做?

// Not real code
let doSomething = async {
let mutable result = null
new Action(fun x -> result <- x) |> Tasks.DoSomething
// Wait for result to be assigned
return result
}

例如,假设DoSomething如下所示:

module Tasks
let DoSomething callback = 
callback "Hello"
()

那么以下内容的输出应该是"Hello"

let wrappedDoSomething = async {
// Call DoSomething somehow
}
[<EntryPoint>]
let main argv =
async {
let! resultOfDoSomething = wrappedDoSomething
Console.WriteLine resultOfDoSomething
return ()
} |> Async.RunSynchronously
0

函数 Async.FromContinuations 可以说是 Async 的"最低级别"。所有其他异步组合器都可以用它来表示。

从某种意义上说,它是最低级别的,因为它直接编码了异步计算的本质 - 在三种可能的情况下该做什么的知识:(1(成功完成前一个计算步骤,(2(前一个计算步骤的崩溃,以及(3(从外部取消。这些可能的情况表示为传递给Async.FromContinuations的函数的三个函数类型参数。例如:

let returnFive = 
Async.FromContinuations( fun (succ, err, cancl) ->
succ 5
)
async {
let! res = returnFive
printfn "%A" res  // Prints "5"
} 
|> Async.RunSynchronously

在这里,我的函数fun (succ, err, cancl) -> succ 5决定它已成功完成,并调用succ延续将其计算结果传递给下一步。

在您的情况下,函数DoSomething仅表示三种情况中的一种 - 即"成功完成后该怎么做"。进入回调后,这意味着无论DoSomething在做什么,都已成功完成。这时你需要调用succ延续:

let doSometingAsync = 
Async.FromContinuations( fun (succ, err, cancl) -> 
Tasks.DoSomething( fun res -> succ res ) 
)

当然,您可以通过将succ作为回调直接传递到DoSomething来避免嵌套的 lambda 表达式fun res -> succ res。不幸的是,您必须显式指定用于包装它的Action类型,这否定了优势:

let doSometingAsync = 
Async.FromContinuations( fun (succ, err, cancl) -> 
Tasks.DoSomething( System.Action<string> succ ) 
)

顺便说一句,请注意,这立即发现了DoSomethingAPI中的一个漏洞:它忽略了错误情况。如果DoSomething没有做它应该做的事情会发生什么?你不可能知道它,整个异步工作流程只会挂起。或者,更糟糕的是:该过程将立即退出(取决于崩溃的发生方式(。

如果您对DoSomething有任何控制权,我建议您解决此问题。

你可以尝试这样的事情:

let doSomething callback = async {
Tasks.DoSomething(callback)
}

如果您的目标是在方法中定义回调,则可以执行以下操作:

let doSomething () = async {
let callback = new Action<string>(fun result -> printfn "%A" result )
Tasks.DoSomething(callback)
}

如果您的目标是在DoSomething回调中使用异步方法的结果,则可以执行以下操作:

let doSomething =
Async.StartWithContinuations(
async {
return result
},
(fun result -> Tasks.DoSomething(result)),
(fun _ -> printfn "Deal with exception."),
(fun _ -> printfn "Deal with cancellation."))

最新更新