考虑以下代码:
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
这样行事?
答案 0 :(得分:1)
异步工作流中处理超时的方式是通过取消。在您的情况下,阻塞等待发生的情况是,检查取消的第一个可能时刻是等待完成之后。
异步工作流使用一种称为协作取消的模型,这意味着主叫方和被叫方通过中介CancellationToken
合作处理取消。当Async.StartChild
需要取消子工作流程时,它将请求对令牌的取消,然后由子工作流程检查取消令牌的状态并调用取消继续。这些检查被烘焙到异步原语中,寻找IsCancellationRequested
here。
由于您的孩子工作流程已在Thread.Sleep
上被阻止,因此只有在睡眠完成后才会发生这种情况。
值得指出的是,TPL任务使用相同的模型。您只是看不到它,因为您的任务超时取决于Task.WhenAny
的语义-并且可能不是what you expect的语义。其余正在运行的任务。