问题
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.
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
}
When the CancellationToken passed to x.AcceptWebSocketAsync is cancelled, returns null, and then AsyncAcceptWebSocket method returns None. I can verify this with a breakpoint.
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"
}
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?)
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.
What is going on here?
回答1:
There are two things about the question:
First, when a cancellation happens in F#, the
AwaitTaskdoes not returnnull, but instead, the task throwsOperationCanceledExceptionexception. So, you do not get backNonevalue, but instead, you get an exception (and then F# also runs yourfinallyblock).The confusing thing is that cancellation is a special kind of exception that cannot be handled in user code inside the
asyncblock - once your computation is cancelled, it cannot be un-cancelled and it will always stop (you can do cleanup infinally). You can workaround this (see this SO answer) but it might cause unexpected things.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.CancellationTokenwhich 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#).
EDIT: Clarified how F# async handles cancellation exceptions.
来源:https://stackoverflow.com/questions/27021497/f-how-asynct-cancellation-works