F# Async.RunSynchronously с тайм-аутом и отменой Token
При вызове Async.RunSynchronously с таймаутом и CancellationToken значение тайм-аута, кажется, игнорируется. Я могу обойти это, вызвав метод CancelAfter для CancellationToken, но в идеале я хотел бы иметь возможность различать исключения, возникающие в рабочем процессе, TimeOutExceptions и OperationCanceledExceptions.
Я полагаю, что пример кода ниже демонстрирует это.
open System
open System.Threading
let work =
async {
let endTime = DateTime.UtcNow.AddMilliseconds(100.0)
while DateTime.UtcNow < endTime do
do! Async.Sleep(10)
Console.WriteLine "working..."
raise ( Exception "worked for more than 100 millis" )
}
[<EntryPoint>]
let main argv =
try
Async.RunSynchronously(work, 50)
with
| e -> Console.WriteLine (e.GetType().Name + ": " + e.Message)
let cts = new CancellationTokenSource()
try
Async.RunSynchronously(work, 50, cts.Token)
with
| e -> Console.WriteLine (e.GetType().Name + ": " + e.Message)
cts.CancelAfter(80)
try
Async.RunSynchronously(work, 50, cts.Token)
with
| e -> Console.WriteLine (e.GetType().Name + ": " + e.Message)
Console.ReadKey(true) |> ignore
0
Выходные данные следующие, показывая, что тайм-аут действует только в первом случае (где не указан CancelationToken)
working...
working...
TimeoutException: The operation has timed out.
working...
working...
working...
working...
working...
working...
working...
Exception: worked for more than 100 millis
working...
working...
working...
working...
working...
working...
OperationCanceledException: The operation was canceled.
Это предполагаемое поведение? Есть ли способ получить поведение, за которым я следую?
Спасибо!
1 ответ
Я не уверен, является ли это предполагаемым поведением - по крайней мере, я не вижу никакой причины, почему это было бы. Однако это поведение реализуется непосредственно при обработке параметров RunSynchronously
, Если вы посмотрите на исходный код библиотеки, вы увидите:
static member RunSynchronously (p:Async<'T>,?timeout,?cancellationToken) =
let timeout,token =
match cancellationToken with
| None -> timeout,(!defaultCancellationTokenSource).Token
| Some token when not token.CanBeCanceled -> timeout, token
| Some token -> None, token
В вашем случае (как с тайм-аутом, так и с токеном отмены, который можно отменить) код проходит через последнюю ветвь и игнорирует тайм-аут. Я думаю, что это либо ошибка, либо это то, что должно быть упомянуто в документации.
В качестве обходного пути вы можете создать отдельный CancellationTokenSource
указать время ожидания и связать его с основным источником отмены, чтобы вызывающий абонент предоставил (используя CreateLinkedTokenSource
). Когда вы получаете OperationCancelledException
Затем вы можете определить, был ли источник фактической отмены или тайм-аута:
type Microsoft.FSharp.Control.Async with
static member RunSynchronouslyEx(a:Async<'T>, timeout:int, cancellationToken) =
// Create cancellation token that is cancelled after 'timeout'
let timeoutCts = new CancellationTokenSource()
timeoutCts.CancelAfter(timeout)
// Create a combined token that is cancelled either when
// 'cancellationToken' is cancelled, or after a timeout
let combinedCts =
CancellationTokenSource.CreateLinkedTokenSource
(cancellationToken, timeoutCts.Token)
// Run synchronously with the combined token
try Async.RunSynchronously(a, cancellationToken = combinedCts.Token)
with :? OperationCanceledException as e ->
// If the timeout occurred, then we throw timeout exception instead
if timeoutCts.IsCancellationRequested then
raise (new System.TimeoutException())
else reraise()