I really like F#'s async
workflow, but as for me, it has one serious issue: it does not allow creating workflows which should execute no longer than some specific timespan.
To make it clearer, here's a simple function I wrote for myself:
let withTimeout operation timeout = async {
try
return Some <| Async.RunSynchronously (operation, timeout)
with :? TimeoutException -> return None
}
I.e. signature is
val withTimeout : operation:Async<'a> -> timeout:int -> Async<'a option>
Example usage here:
let op = async {
do! Async.Sleep(1000)
return 1
}
#time
withTimeout op 2000 |> Async.RunSynchronously;;
// Real: 00:00:01.116, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0
// val it : unit option = Some 1
withTimeout op 2000 |> Async.RunSynchronously;;
// Real: 00:00:01.004, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
// val it : unit option = Some 1
withTimeout op 500 |> Async.RunSynchronously;;
// Real: 00:00:00.569, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
// val it : unit option = None
You can see, it works as expected. And it's very nice, but it is also a bit awkward and I'm not sure of it's safety and other issues which might arise. Maybe I am reinventing the wheel, and there's nice and concise way to write such workflows?
UPD: Best option at the moment was proposed by Vesa A.J.K here: https://stackoverflow.com/a/26230245/1554463. With my edits its like that:
type Async with
static member WithTimeout (timeout : int option) operation =
match timeout with
| Some time when time > 0 ->
async {
let! child = Async.StartChild (operation, time)
try
let! result = child
return Some result
with :? TimeoutException -> return None
}
| _ ->
async {
let! result = operation
return Some result
}
Here's another option:
type Async with
static member WithCancellation (token:CancellationToken) operation =
async {
try
let task = Async.StartAsTask (operation, cancellationToken = token)
task.Wait ()
return Some task.Result
with
| :? TaskCanceledException -> return None
| :? AggregateException -> return None
}
static member WithTimeout (timeout:int option) operation =
match timeout with
| Some(time) ->
async {
use tokenSource = new CancellationTokenSource (time)
return! operation |> Async.WithCancellation tokenSource.Token
}
| _ ->
async {
let! res = operation
return Some res
}
Here I use .Net tasks and CancellationToken
.
Just use Async.StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
:
let with_timeout timeout action =
async {
let! child = Async.StartChild( action, timeout )
return! child
}
Please see this implementation of Async.WhenAny
, which is supposed to behave similar to Task.WhenAny
.
By using it, you can implement withTimeout
similarly to how it is implemented for Task
here:
let withTimeout dueTime comp =
let success = async {
let! x = comp
return (Some x)
}
let timeout = async {
do! Async.Delay(dueTime)
return None
}
Async.WhenAny(success, timeout)
I'm not confident that it will cancel the other computation when the first one finishes, but at least this implementation won't block a thread unnecessarily.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With