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

时间:2018-06-10 10:23:17

标签: asynchronous 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

2 个答案:

答案 0 :(得分:6)

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

它是直接编码异步计算本质的意义上的最低级别 - 在三种可能情况下做什么的知识:(1)成功完成先前的计算步骤,(2)a先前计算步骤的崩溃,以及(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 continuation将其计算结果传递给下一步。

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

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

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

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

顺便说一句,请注意,这立即在DoSomething的API中发现了一个漏洞:它忽略了错误情况。如果DoSomething无法做任何意图,会发生什么?您无法了解它,整个异步工作流程将会挂起。或者,更糟糕的是:进程将立即退出(取决于崩溃的发生方式)。

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

答案 1 :(得分:4)

您可以尝试以下内容:

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