Reputation: 37095
In JavaScript, there is a function called Promise.race
that takes a list of promises and returns a new promise that completes when any of the input promises completes.
See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
F# has Async.Parallel
, which completes when all of the input asyncs have completed, but it does not seem to have an equivalent for any (such as Async.Race
).
How can I write this in F#?
Upvotes: 1
Views: 195
Reputation: 37095
You could use tasks.
Something like this:
let race (xs : Async<'t> seq) : Async<'t> =
async {
let! ct = Async.CancellationToken
let! t =
xs
|> Seq.map (fun x -> Async.StartAsTask(x, cancellationToken = ct))
|> Task.WhenAny
|> Async.AwaitTask
return! Async.AwaitTask t
}
Upvotes: 3
Reputation: 37095
Another option is to (ab)use the exception mechanism to get an early return.
[<RequireQualifiedAccess>]
module Async =
open System
/// An exception that carries a success value
type internal ShortCircuit<'t>(value : 't) =
inherit Exception()
member this.Value = value
let race (a : Async<'a>) (b : Async<'b>) : Async<Choice<'a * Async<'b>, Async<'a> * 'b>> =
async {
let! a = Async.StartChild a
let! b = Async.StartChild b
let x =
async {
let! a = a
let choice : Choice<'a * Async<'b>, Async<'a> * 'b> =
Choice1Of2(a, b)
raise (ShortCircuit choice)
}
let y =
async {
let! b = b
let choice : Choice<'a * Async<'b>, Async<'a> * 'b> =
Choice2Of2(a, b)
raise (ShortCircuit choice)
}
try
do!
Async.Parallel([| x; y |])
|> Async.Ignore
return failwith "Unreachable"
with :? ShortCircuit<Choice<'a * Async<'b>, Async<'a> * 'b>> as sc ->
return sc.Value
}
Usage:
let foo =
async {
printfn "foo started"
do! Async.Sleep 1000
return "foo"
}
let bar =
async {
printfn "bar started"
do! Async.Sleep 5000
return "bar"
}
async {
printfn "Racing..."
match! Async.race foo bar with
| Choice1Of2 (a, b) ->
printfn $"a = %s{a}"
let! b = b
printfn $"b = %s{b}"
| Choice2Of2 (a, b) ->
printfn $"b = %s{b}"
let! a = a
printfn $"a = %s{a}"
}
|> Async.RunSynchronously
Upvotes: 0
Reputation: 37095
Using Async.Choice
from FSharp.Control
:
let race xs =
async {
let! first =
xs
|> Seq.map (fun task -> async {
let! x = task
return Some x
})
|> Async.Choice
return Option.get first
}
Upvotes: 1