Async.StartChild с тайм-аутом и ожиданием синхронизации внутри дочерней асинхронной - PullRequest
1 голос
/ 13 апреля 2019

Рассмотрим следующий код:

open System
open System.Diagnostics
open System.Threading
open System.Threading.Tasks

type Async with
    static member WithTimeout (timeout: int) operation =
        async {
            let! child = Async.StartChild (operation, timeout)
            try
                let! _result = child
                return true
            with :? TimeoutException -> return false
        }

    static member WithTaskTimeout<'T> (timeout: int) (operation: Async<'T>) = async {
        let delay = Task.Delay(timeout)

        let! task = Task.WhenAny(operation |> Async.StartAsTask :> Task, delay) |> Async.AwaitTask
        if task = delay then
            return false
        else
            return true
    }

[<EntryPoint>]
let main _ =
    let syncSleep = async {
        Thread.Sleep(4000)
        return 1
    }

    let asyncSleep = async {
        do! Async.Sleep(4000)
        return 1
    }

    let run name async =
        let time action prefix =
            let sw = Stopwatch.StartNew()
            let result = action |> Async.RunSynchronously
            sw.Stop()

            printfn "%s | %s returned %O. Elapsed: %O" prefix name result sw.Elapsed

        time (async |> Async.WithTimeout 2000) "Async"
        time (async |> Async.WithTaskTimeout 2000) "Task "

    run "Thread.Sleep" syncSleep
    run "Async.Sleep " asyncSleep

    0

В Mono 5.18.1.3 он выдает следующий вывод:

Async | Thread.Sleep returned False. Elapsed: 00:00:04
Task  | Thread.Sleep returned False. Elapsed: 00:00:02
Async | Async.Sleep  returned False. Elapsed: 00:00:02
Task  | Async.Sleep  returned False. Elapsed: 00:00:02

Таким образом, когда дочерняя асинхронная синхронизация имеет синхронное ожидание внутри, Async.StartChild возвращаетне когда истекает время ожидания, а когда внутренняя асинхронность завершается.В то же время выполнение на основе задач с тайм-аутом в обоих вызовах возвращается только после тайм-аута.

Почему Async.StartChild ведет себя так?

1 Ответ

1 голос
/ 13 апреля 2019

Тайм-ауты обрабатываются в асинхронных рабочих процессах через отмены.Что происходит в вашем сценарии с блокирующим ожиданием, так это то, что первый возможный момент для проверки отмены наступает после завершения ожидания.

В асинхронных рабочих процессах используется модель, называемая кооперативным аннулированием, что означает, что вызывающий и вызываемый абоненты взаимодействуют при обработке аннулирования через посредника CancellationToken.Когда Async.StartChild необходимо отменить дочерний рабочий процесс, он запросит отмену на токене, а затем он перейдет к дочернему рабочему процессу, чтобы проверить состояние токена отмены и вызвать продолжение отмены.Эти проверки запекаются в асинхронных примитивах, ищите IsCancellationRequested здесь .

Поскольку ваш дочерний рабочий процесс заблокирован на Thread.Sleep, этого не произойдет, пока не будет завершен сон.

Стоит отметить, что та же модель используется в задачах TPL.Вы просто не видите его, потому что время ожидания вашей задачи зависит от семантики Task.WhenAny - и это может быть не того, что вы ожидаете относительно оставшихся запущенных задач.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...