vto*_*ola 10 f# cancellation f#-3.0 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
}
Run Code Online (Sandbox Code Playgroud)
当CancellationToken传递给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"
}
Run Code Online (Sandbox Code Playgroud)
然后我在返回finally时放入执行的内容,但是我仍然没有执行的代码.(实际上,它会为每个连接的客户端在块上打印一次消息,所以也许我应该采用迭代方法?)listener.AsyncAcceptWebSocketNonematchfinally
但是,如果我使用自定义CancellationToken而不是Async.DefaultCancellationToken,一切都按预期工作,并"Stop accepting clients.\n"在屏幕上打印消息.
这里发生了什么?
Tom*_*cek 13
关于这个问题有两件事:
首先,当在F#中发生取消时,AwaitTask不会返回null,而是该任务抛出OperationCanceledException异常.所以,你没有获得None价值,而是获得一个例外(然后F#也运行你的finally块).
令人困惑的是,取消是一种特殊的异常,无法在async块内的用户代码中处理- 一旦你的计算被取消,它就不能被取消,它将永远停止(你可以进行清理finally).您可以解决此问题(请参阅此SO答案),但这可能会导致意外情况.
其次,我不会使用默认取消令牌 - 这是所有异步工作流共享的,所以它可能会做出意想不到的事情.您可以使用Async.CancellationToken它来访问当前的取消令牌(F#会自动为您传播 - 因此您不必像在C#中那样手动传递它).
编辑:澄清了F#异步如何处理取消异常.