我对在C#中使用TPL进行异步取消操作非常熟悉,但是我对在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
}
当传递给x.AcceptWebSocketAsync
的CancellationToken
被取消时,返回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"
}
当listener.AsyncAcceptWebSocket
返回None
时,我放在finally
中的内容会被执行,但我放在match
中的代码仍然没有被执行。(实际上,它会为每个连接的客户端在finally
块中打印一次消息,所以也许我应该采用迭代方法?)
然而,如果我使用自定义的CancellationToken
而不是Async.DefaultCancellationToken
,则一切都按预期工作,并且"Stop accepting clients.\n"
消息会被打印在屏幕上。
这里到底发生了什么?
AcceptWebSocketAsync
是一个在C#库中声明的方法,它接受一个CancellationToken
。当取消操作被触发时,该方法返回null
而不是一个WebSocket
。这种设计是特意为之。 - vtortolaCancellationTokenSource
(而不是将其传递给Async.Start
)。 - Tomas PetricekAsync.Start
,那么当该令牌被取消时,工作流程将被取消,并且在清理期间只有finally
块将运行。因此,您需要为该方法使用单独的令牌(如果不想取消工作流程,则不要将其传递给Async.Start
)。 - Tomas Petricek