F#异步方式取消工程? [英] F# How Async<'T> cancellation works?
问题描述
我对TPL在C#中完成异步取消的方式感到非常满意,但是在F#中我有点困惑.显然,通过调用Async.CancelDefaultToken()
足以取消传出的Async<'T>
操作.但是它们并没有像我预期的那样被取消,它们只是……消失了……我无法正确检测到取消并正确地拆除了堆栈.
I was pretty comfortable with how async cancellations where done in C# with the TPL, but I am a little bit confused in F#. Apparently by calling Async.CancelDefaultToken()
is enough to cancel outgoing Async<'T>
operations. But they are not cancelled as I expected, they just... vanishes... I cannot detect properly the cancellation and tear down the stack properly.
例如,我有依赖于使用TPL的C#库的以下代码:
For example, I have this code that depends on a C# library that uses TPL:
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
.我可以使用断点来验证这一点.
When the CancellationToken
passed to x.AcceptWebSocketAsync
is cancelled, returns null
, and then AsyncAcceptWebSocket
method returns None
. I can verify this with a breakpoint.
但是,AsyncAcceptClients
(调用方)永远不会获得该None
值,该方法只是结束,并且"Stop accepting clients.\n"
永远不会显示在控制台上.如果我将所有内容包装在try\finally
中:
But, AsyncAcceptClients
(the caller), never gets that None
value, the method just ends, and "Stop accepting clients.\n"
is never displayed on the console. If I wrap everything in a 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
块上打印一次消息,所以也许我应该采用迭代方法?)
Then what I put in the finally
gets executed when listener.AsyncAcceptWebSocket
returns None
, but the code I have in the match
still doesn't. (Actually, it prints the message on the finally
block once for each connected client, so maybe I should move to an iterative approach?)
但是,如果我使用自定义的CancellationToken
而不是Async.DefaultCancellationToken
,则一切正常,并且"Stop accepting clients.\n"
消息将显示在屏幕上.
However, if I use a custom CancellationToken
rather than Async.DefaultCancellationToken
, everything works as expected, and the "Stop accepting clients.\n"
message is print on screen.
这是怎么回事?
推荐答案
这个问题有两件事:
-
首先,当F#中发生取消时,
AwaitTask
不返回null
,而是任务抛出OperationCanceledException
异常.因此,您没有获得None
值,而是得到了一个异常(然后F#也运行了finally
块).
First, when a cancellation happens in F#, the
AwaitTask
does not returnnull
, but instead, the task throwsOperationCanceledException
exception. So, you do not get backNone
value, but instead, you get an exception (and then F# also runs yourfinally
block).
令人困惑的是,取消是一种特殊的异常,无法在async
块内的用户代码中处理-一旦取消计算,就不能取消它,并且它总是停止(您可以这样做finally
中的清除).您可以解决此问题(请参见此SO答案),但这可能会导致意外的情况.
The confusing thing is that cancellation is a special kind of exception that cannot be handled in user code inside the async
block - once your computation is cancelled, it cannot be un-cancelled and it will always stop (you can do cleanup in finally
). You can workaround this (see this SO answer) but it might cause unexpected things.
第二,我不会使用默认的取消令牌-所有异步工作流程都共享该令牌,因此它可能会执行意外的操作.相反,您可以使用Async.CancellationToken
,它使您可以访问当前的取消令牌(F#自动为您传播-因此您不必像在C#中那样手动传递它).
Second, I would not use default cancellation token - that's shared by all async workflows and so it might do unexpected things. You can instead use Async.CancellationToken
which gives you access to a current cancellation token (which F# automatically propagates for you - so you do not have to pass it around by hand as you do in C#).
编辑:阐明了F#异步处理取消异常的方式.
EDIT: Clarified how F# async handles cancellation exceptions.
这篇关于F#异步方式取消工程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!