Async.StartChild与超时和同步在子异步内部等待

时间:2019-04-13 07:19:18

标签: asynchronous f#

考虑以下代码:

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 个答案:

答案 0 :(得分:1)

异步工作流中处理超时的方式是通过取消。在您的情况下,阻塞等待发生的情况是,检查取消的第一个可能时刻是等待完成之后。

异步工作流使用一种称为协作取消的模型,这意味着主叫方和被叫方通过中介CancellationToken合作处理取消。当Async.StartChild需要取消子工作流程时,它将请求对令牌的取消,然后由子工作流程检查取消令牌的状态并调用取消继续。这些检查被烘焙到异步原语中,寻找IsCancellationRequested here

由于您的孩子工作流程已在Thread.Sleep上被阻止,因此只有在睡眠完成后才会发生这种情况。

值得指出的是,TPL任务使用相同的模型。您只是看不到它,因为您的任务超时取决于Task.WhenAny的语义-并且可能不是what you expect的语义。其余正在运行的任务。