Отмененная задача не возвращает управление асинхронному блоку
Я попытался свести это к самому маленькому возможному упреку, но это все еще немного длинновато, мои извинения.
У меня есть проект 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
1 ответ:
Что в конечном счете вызывает такое поведение, так это то, что отмены являются особыми в F# Async. Аннулирование эффективно переводится в остановку и срыв . Как вы можете видеть в источнике , отмена в
Task
полностью исключает вычисление.Если вы хотите старый добрый
OperationCanceledException
, который вы можете обрабатывать как часть ваших вычислений, мы можем просто сделать наш собственный.Отмена-это теперь просто еще одно исключение , и с исключениями мы можем справиться. Вот это да! репро:type Async = static member AwaitTaskWithCancellations (task: Task<_>) = Async.FromContinuations(fun (setResult, setException, setCancelation) -> task.ContinueWith(fun (t:Task<_>) -> match t.Status with | TaskStatus.RanToCompletion -> setResult t.Result | TaskStatus.Faulted -> setException t.Exception | TaskStatus.Canceled -> setException <| OperationCanceledException() | _ -> () ) |> ignore )
let tcs = TaskCompletionSource<unit>() tcs.SetCanceled() async { try let! result = tcs.Task |> Async.AwaitTaskWithCancellations return result with | :? OperationCanceledException -> printfn "cancelled" | ex -> printfn "faulted %A" ex () } |> Async.RunSynchronously