I tried to reduce this to the smallest possible reproduction, but it is still a little long, my apologies.
I have a F # project that references a C # project with code like the following.
public static class CSharpClass { public static async Task AsyncMethod(CancellationToken cancellationToken) { await Task.Delay(3000); cancellationToken.ThrowIfCancellationRequested(); } }
Here's the code F #.
type Message = | Work of CancellationToken | Quit of AsyncReplyChannel<unit> let mkAgent() = MailboxProcessor.Start <| fun inbox -> let rec loop() = async { let! msg = inbox.TryReceive(250) match msg with | Some (Work cancellationToken) -> let! result = CSharpClass.AsyncMethod(cancellationToken) |> Async.AwaitTask |> Async.Catch // THIS POINT IS NEVER REACHED AFTER CANCELLATION match result with | Choice1Of2 _ -> printfn "Success" | Choice2Of2 exn -> printfn "Error: %A" exn return! loop() | Some (Quit replyChannel) -> replyChannel.Reply() | None -> return! loop() } loop() [<EntryPoint>] let main argv = let agent = mkAgent() use cts = new CancellationTokenSource() agent.Post(Work cts.Token) printfn "Press any to cancel." System.Console.Read() |> ignore cts.Cancel() printfn "Cancelled." agent.PostAndReply Quit printfn "Done." System.Console.Read()
The problem is that after cancellation, control never returns to the asynchronous block. I'm not sure if it hangs in AwaitTask or Catch . Intuition tells me that it blocks when I try to return to the previous synchronization context, but I'm not sure how to confirm this. I am looking for ideas on how to fix this problem, or maybe someone with a deeper understanding here can identify the problem.
POSSIBLE SOLUTION
let! result = Async.FromContinuations(fun (cont, econt, _) -> let ccont e = econt e let work = CSharpClass.AsyncMethod(cancellationToken) |> Async.AwaitTask Async.StartWithContinuations(work, cont, econt, ccont)) |> Async.Catch
asynchronous f # task cancellation mailboxprocessor
Daniel
source share