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