【问题标题】:Async.StartChild with timeout and sync wait inside of child asyncAsync.StartChild 在子异步内部具有超时和同步等待
【发布时间】:2019-04-13 07:19:17
【问题描述】:

考虑以下代码:

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 会这样?

【问题讨论】:

    标签: asynchronous f#


    【解决方案1】:

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

    异步工作流使用称为协作取消的模型,这意味着调用者和被调用者通过中介CancellationToken 合作处理取消。当Async.StartChild需要取消子工作流时,它会在token上请求取消,然后落到子工作流去检查取消token的状态并调用cancel continue。这些检查被烘焙到异步原语中,寻找IsCancellationRequestedhere

    由于您的子工作流程在 Thread.Sleep 上被阻止,因此在睡眠完成之前不会发生这种情况。

    值得注意的是,TPL 任务使用相同的模型。您只是看不到它,因为您的任务超时取决于 Task.WhenAny 的语义 - 而那些可能不是 what you expect w.r.t。剩余的运行任务。

    【讨论】:

      猜你喜欢
      • 2016-09-04
      • 1970-01-01
      • 2021-10-22
      • 2017-12-07
      • 1970-01-01
      • 2021-05-25
      • 2018-12-24
      相关资源
      最近更新 更多