Asynchronous OnceAsync:只运行f#async函数一次

Asynchronous OnceAsync:只运行f#async函数一次,asynchronous,f#,Asynchronous,F#,我正在尝试编写一个函数(OnceAsync f),以确保异步函数在服务器(即多线程环境)上只运行一次。我原以为这很容易,但很快就变得复杂了(锁,忙碌的等待!!) 这是我的解决方案,但我认为它设计过度;一定有更好的办法。这应该适用于FSI: let locked_counter init = let c = ref init fun x -> lock c <| fun () -> c := !c + x !c let wait_

我正在尝试编写一个函数(OnceAsync f),以确保异步函数在服务器(即多线程环境)上只运行一次。我原以为这很容易,但很快就变得复杂了(锁,忙碌的等待!!)

这是我的解决方案,但我认为它设计过度;一定有更好的办法。这应该适用于FSI:

let locked_counter init =
    let c = ref init
    fun x -> lock c <| fun () -> 
        c := !c + x
        !c
let wait_until finished = async {  
    while not(finished()) do
        do! Async.Sleep(1000) 
}

let OnceAsync f = 
    // - ensure that the async function, f, is only called once
    // - this function always returns the value, f()
    let mutable res = None
    let lock_inc = locked_counter 0

    async {
        let count = lock_inc 1

        match res, count with
        | None, 1 ->    // 1st run
            let! r = f
            res <- Some r
        | None, _ ->    // nth run, wait for 1st run to finish
            do! wait_until (fun() -> res.IsSome)
        | _ -> ()       // 1st run done, return result

        return res.Value
    }
let locked_counter init=
设c=refinit
乐趣x->锁c
c:=!c+x
!C
让我们等待_直到完成=异步{
虽然没有(finished())做
do!Async.Sleep(1000)
}
设OnceAsync f=
//-确保异步函数f只被调用一次
//-此函数始终返回值f()
设可变res=None
设lock_inc=locked_计数器0
异步的{
let count=锁紧装置1
匹配res,用
|无,1->//第一次运行
设!r=f
res//n次运行,等待第1次运行完成
做!等到(fun()->res.IsSome)
|->()//第一次运行完成,返回结果
返回res.值
}
您可以使用此代码测试OnceAsync是否正确:

let test() =
    let mutable count = 0

    let initUser id = async {
        do! Async.Sleep 1000 // simulate work
        count <- count + 1
        return count
    }

    //let fmem1 = (initUser "1234")
    let fmem1 = OnceAsync (initUser "1234")

    async {
        let ps = Seq.init 20 (fun i -> fmem1)
        let! rs = ps |> Async.Parallel
        printfn "rs = %A" rs     // outputs: [|1; 1; 1; 1; 1; ....; 1|]
    }

test() |> Async.Start 
let test()=
设可变计数=0
让initUser id=async{
do!Async.Sleep 1000//模拟工作
计数(fmem1)
让!rs=ps |>Async.Parallel
printfn“rs=%A”rs//输出:[1;1;1;1;1;…;1 |]
}
test()|>Async.Start

如果合适,最简单的方法就是使用
Async.StartChild
。与您的解决方案不同,它会导致函数运行,即使结果从未实际使用过,例如在
Seq.init 0
情况下

//let fmem1 = OnceAsync (initUser "1234")

async {
  let! fmem1 = Async.StartChild (initUser "1234")
  let ps = Seq.init 20 (fun i -> fmem1)
  let! rs = ps |> Async.Parallel
  printfn "rs = %A" rs     // outputs: [|1; 1; 1; 1; 1; ....; 1|]
} |> Async.RunSynchronously
与您最相似的最简单方法是使用
TaskCompletionSource
,如下所示:

let OnceAsync f = 
  let count = ref 0
  let tcs = TaskCompletionSource<_>()
  async {
    if Interlocked.Increment(count) = 1 then
      let! r = f
      tcs.SetResult r
    return! Async.AwaitTask tcs.Task
  }

美好的我想摆脱锁和等待,但这比我拥有的要好得多。@Ray我添加了一个
MailboxProcessor
示例,但IIRC确实在引擎盖下使用锁,所以我不知道你是否能完全摆脱它们。看起来我无法摆脱锁。TaskCompletionSource解决方案有效。我不能使用联锁。但是增量;我只做我自己的版本。
let OnceAsync f = 
  let handler (agent: MailboxProcessor<AsyncReplyChannel<_>>) =
    let rec run resultOpt =
      async {
        let! chan = agent.Receive()
        let! result = 
          match resultOpt with
          | None -> f
          | Some result -> async.Return result
        chan.Reply result
        return! run (Some result)
      }
    run None
  let mbp = MailboxProcessor.Start handler
  async { return! mbp.PostAndAsyncReply id }