我试图将这个问题简化到最小的可重现状态,但它仍然比较长,抱歉。
我有一个F#项目,引用了一个包含以下代码的C#项目。
public static class CSharpClass {
public static async Task AsyncMethod(CancellationToken cancellationToken) {
await Task.Delay(3000);
cancellationToken.ThrowIfCancellationRequested();
}
}
下面是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()
问题在于,一旦取消操作,控制权永远不会返回到异步块。我不确定它是否在
AwaitTask
或Catch
中挂起。直觉告诉我,在尝试返回到先前的同步上下文时会阻塞,但我不确定如何确认这一点。我正在寻找解决此问题的方法,或者也许有更深入了解的人可以发现问题。可能的解决方案:
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
OperationCanceledException
在F#中有特殊处理,总是会停止整个异步过程,一直到最上层。请参考这个答案:https://dev59.com/ll8d5IYBdhLWcg3wQwa5#27022315 - Fyodor SoikinOperationCanceledException
的处理方式。你甚至可以自己抛出它,而不涉及令牌,它仍然会停止整个异步操作。 - Fyodor Soikin