Consider the following code:
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
On Mono 5.18.1.3 it produces the following output:
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
So when child async has synchronous wait inside, Async.StartChild
returns not when timeout passes, but when the inner async comes to a completion.
At the same time task-based execute with timeout in both calls returns just only after timeout.
Why Async.StartChild
behaves this way?