F# Async.RunSynchronously с тайм-аутом и CancelToken

При вызове 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.

Это предполагаемое поведение? Есть ли способ получить поведение, которое мне нужно?

Спасибо!


person Joe Taylor    schedule 21.01.2013    source источник


Ответы (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()
person Tomas Petricek    schedule 21.01.2013
comment
PS: я сообщил об этом команде F# (если вы обнаружите другие проблемы, вы можете использовать fsbugs at microsoft dot com) - person Tomas Petricek; 21.01.2013
comment
Я не уверен, когда это было добавлено, но документация на msdn.microsoft.com /en-us/library/ee370262.aspx говорит, что If you provide a cancelable cancellation token, the timeout is ignored. - person Grozz; 12.05.2014
comment
@TomasPetricek, у тебя случайно нет ссылки на этот старый отчет? Кстати, сегодня я задал вопрос, аналогичный этому, но без синхронной части: stackoverflow.com/questions/54978550/ - person knocte; 04.03.2019