sdgfsdh
sdgfsdh

Reputation: 37095

F# async equivalent of Promise.race?

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

Answers (3)

sdgfsdh
sdgfsdh

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

sdgfsdh
sdgfsdh

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

sdgfsdh
sdgfsdh

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

Related Questions