F#异步<'T>取消工作?

时间:2014-11-19 16:08:39

标签: f# f#-3.0 cancellation f#-async

我对使用TPL在C#中完成异步取消的方式非常满意,但我对F#有点困惑。显然,通过调用Async.CancelDefaultToken()就足以取消传出的Async<'T>操作。但它们并没有像我预期的那样被取消,它们只是......消失了...我无法正确检测到取消并正确拆除堆栈。

例如,我有这个代码依赖于使用TPL的C#库:

type WebSocketListener with
  member x.AsyncAcceptWebSocket = async {
    let! client = Async.AwaitTask <| x.AcceptWebSocketAsync Async.DefaultCancellationToken
    if(not(isNull client)) then
        return Some client
    else 
        return None
  }

let rec AsyncAcceptClients(listener : WebSocketListener) =
  async {
    let! result = listener.AsyncAcceptWebSocket
    match result with
        | None -> printf "Stop accepting clients.\n"
        | Some client ->
            Async.Start <| AsyncAcceptMessages client
            do! AsyncAcceptClients listener
  }

取消传递给CancellationToken的{​​{1}}后,返回x.AcceptWebSocketAsync,然后null方法返回AsyncAcceptWebSocket。我可以通过断点验证这一点。

但是,None(调用者)永远不会获得AsyncAcceptClients值,方法才会结束,并且None永远不会显示在控制台上。如果我将所有内容都包装在"Stop accepting clients.\n"

try\finally

然后当let rec AsyncAcceptClients(listener : WebSocketListener) = async { try let! result = listener.AsyncAcceptWebSocket match result with | None -> printf "Stop accepting clients.\n" | Some client -> Async.Start <| AsyncAcceptMessages client do! AsyncAcceptClients listener finally printf "This message is actually printed" } 返回finally时,listener.AsyncAcceptWebSocket中的内容会被执行,但None中的代码仍然没有。 (实际上,它会为每个连接的客户端在match块上打印一次消息,所以也许我应该采用迭代方法?)

但是,如果我使用自定义finally而不是CancellationToken,则所有内容都会按预期工作,并且Async.DefaultCancellationToken消息会在屏幕上显示。

这里发生了什么?

1 个答案:

答案 0 :(得分:12)

这个问题有两个方面:

  • 首先,当F#中发生取消时,AwaitTask不会返回null,而是会导致OperationCanceledException异常。因此,您不会获得None值,而是获得异常(然后F#也会运行您的finally块。)

    令人困惑的是,取消是一种特殊的异常,无法在async块内的用户代码中处理 - 一旦您的计算被取消,它就不会被取消而且它将永远停止(您可以在finally)进行清理。您可以解决此问题(请参阅this SO answer),但这可能会导致意外情况。

  • 其次,我不会使用默认取消令牌 - 这是所有异步工作流共享的,所以它可能会做出意想不到的事情。您可以使用Async.CancellationToken来访问当前的取消令牌(F#会自动为您传播 - 因此您不必像在C#中那样手动传递它)。

编辑:阐明F#异步如何处理取消例外。