Search code examples
asynchronousf#f#-3.0cancellationtokensource

F# handling Task cancellation


I am struggling to understand why some code is never executed.

Consider this extension method:

type WebSocketListener with
  member x.AsyncAcceptWebSocket = async {
    try
        let! client = Async.AwaitTask <| x.AcceptWebSocketAsync Async.DefaultCancellationToken
        if(not (isNull client)) then
            return Some client
        else
            return None
    with
        | :? System.Threading.Tasks.TaskCanceledException -> 
        | :? AggregateException ->
            return None
  }

I know that AcceptSocketAsync throws a TaskCanceledException when the cancellation token is canceled. I have checked in a C# application. The idea is to return None.

However, that never happens. If I put a breakpoint in the last return None or even in the if expression it never stops there when the cancellation token has been cancelled. And I know it is awaiting in the Async.AwaitTask because if before cancelling, other client connects, it works and it stops in the breakpoints.

I am a little bit lost, why is the exception lost?


Solution

  • Cancellation uses a special path in F# asyncs - Async.AwaitTask will re-route execution of cancelled task to the cancellation continuation. If you want different behavior - you can always do this by manually:

    type WebSocketListener with
      member x.AsyncAcceptWebSocket = async {
        let! ct = Async.CancellationToken
        return! Async.FromContinuations(fun (s, e, c) ->
            x.AcceptWebSocketAsync(ct).ContinueWith(fun (t: System.Threading.Tasks.Task<_>) -> 
                if t.IsFaulted then e t.Exception
                elif t.IsCanceled then s None // take success path in case of cancellation
                else 
                match t.Result with
                | null -> s None
                | x -> s (Some x)
            )
            |> ignore
        )
      }