Timeouts in F#
Timeouts are important for programs that connect to external resources or that otherwise need to bound execution time. Implementing timeouts in F# is straightforward using asynchronous workflows and the Async.Race
function.
open System
open System.Threading.Tasks
let simulateExternalCall delay result =
async {
do! Async.Sleep(delay)
return result
}
let timeout duration =
async {
do! Async.Sleep duration
return None
}
let main() =
// For our example, suppose we're executing an external
// call that returns its result after 2 seconds.
let externalCall1 = simulateExternalCall 2000 "result 1"
// Here we implement a timeout using Async.Race.
// It will run both the external call and the timeout
// concurrently, returning the result of whichever
// finishes first.
let result1 =
Async.RunSynchronously(
Async.Race(externalCall1, timeout 1000))
match result1 with
| Choice1Of2 res -> printfn "%s" res
| Choice2Of2 _ -> printfn "timeout 1"
// If we allow a longer timeout of 3s, then the external
// call will succeed and we'll print the result.
let externalCall2 = simulateExternalCall 2000 "result 2"
let result2 =
Async.RunSynchronously(
Async.Race(externalCall2, timeout 3000))
match result2 with
| Choice1Of2 res -> printfn "%s" res
| Choice2Of2 _ -> printfn "timeout 2"
main()
Running this program shows the first operation timing out and the second succeeding.
$ dotnet fsi timeouts.fsx
timeout 1
result 2
In this F# implementation, we use Async.Sleep
to simulate delays and external calls. The Async.Race
function is used to implement the timeout mechanism, which is similar to the select
statement in the original example. It runs two asynchronous workflows concurrently and returns the result of whichever finishes first.
The Choice1Of2
and Choice2Of2
in the pattern matching correspond to the success and timeout cases respectively. This approach provides a clean and idiomatic way to handle timeouts in F#.