F#事件在Async工作流中不起作用
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了F#事件在Async工作流中不起作用相关的知识,希望对你有一定的参考价值。
我想对代理人进行Post-Fire-Reply。基本上,代理触发事件然后回复调用者。但是,我要么继续收到超时错误,要么事件无法正确触发。我尝试过Post-Fire,它停止了超时错误,但事件没有触发。
let evt = new Event<int>()
let stream = evt.Publish
type Agent<'T> = MailboxProcessor<'T>
type Fire = Fire of int
let agent = Agent.Start(fun inbox ->
let rec loop() = async {
let! msg = inbox.Receive()
let (Fire i) = msg
evt.Trigger i }
loop())
let on i fn =
stream
|> Observable.filter (fun x -> x = i)
|> Observable.filter (fun x -> x <> 1)
|> Observable.subscribe (fun x -> fn x)
let rec collatz n =
printfn "%d" n
on n (fun i ->
if (i % 2 = 0) then collatz (i/2)
else collatz (3*n + 1)) |> ignore
agent.Post (Fire n) // this does not work
// evt.Trigger n // this does works
collatz 13
这是一个简单的实验,它重复创建一个函数来查找Collatz系列中的下一个数字,然后调用自身返回值,直到达到1。
似乎发生的事情是触发器只触发一次。我尝试了Async.RunSynchronously / Async.Start / StartChild / SynchronizationContext的每个组合,我能想到但没有进展。我发现了一个类似于我正在做的blog,但这对我没有帮助
编辑感谢Fyodor Soikin指出我的疏忽。最初的问题仍然存在,我希望同时触发事件并回复结果,但是会超时。
let evt = new Event<int>()
let stream = evt.Publish
type Agent<'T> = MailboxProcessor<'T>
type Command =
| Fire of int
| Get of int * AsyncReplyChannel<int>
let agent = Agent.Start(fun inbox ->
let rec loop() = async {
let! msg = inbox.Receive()
match msg with
| Fire i -> evt.Trigger i
| Get (i,ch) ->
evt.Trigger i
ch.Reply(i)
return! loop() }
loop())
let on i fn =
stream
|> Observable.filter (fun x -> x = i)
|> Observable.filter (fun x -> x <> 1)
|> Observable.subscribe (fun x -> fn x)
let rec collatz n =
printfn "%d" n
on n (fun i ->
if (i % 2 = 0) then collatz (i/2)
else collatz (3*n + 1)) |> ignore
agent.PostAndReply (fun ch -> (Get (n, ch))) |> ignore // timeout
agent.PostAndAsyncReply (fun ch -> (Get (n, ch))) |> Async.Ignore |> Async.Start // works but I need the result
agent.PostAndAsyncReply (fun ch -> (Get (n, ch))) |> Async.RunSynchronously |> ignore // timeout
collatz 13
你的loop
函数不循环。它接收第一条消息,触发事件,然后只是...退出。永远不要尝试接收第二条消息。
您需要使该功能连续工作:处理第一条消息,然后返回接收下一条消息,然后接收下一条消息,依此类推。像这样:
let agent = Agent.Start(fun inbox ->
let rec loop() = async {
let! msg = inbox.Receive()
let (Fire i) = msg
evt.Trigger i
return! loop() }
loop())
编辑
由于您已达到问题的限制,我将在此处回答您的修改。
您在第二个代码段中获得超时的原因是您的代码中存在死锁。让我们跟踪执行情况以查看它。
- 线程1:代理启动。
- 线程2:第一次
collatz
电话。 - 线程2:第一个
collatz
呼叫向代理发布消息。 - 线程1:代理接收消息。
- 线程1:代理触发事件。
- 线程1:由于事件,第二次
collatz
呼叫发生。 - 线程1:第二个
collatz
呼叫向代理发布消息。 - 线程1:第二次
collatz
呼叫开始等待代理响应。
这就是执行结束的地方。代理程序此时无法响应(事实上,它甚至无法接收下一条消息!),因为它的指令指针仍在evt.Trigger
中。 evt.Trigger
调用尚未返回,因此loop
函数尚未递归,因此尚未调用inbox.Receive
函数,因此第二条消息仍在代理队列中等待。
所以你给自己一个经典的僵局:collatz
正在等待代理接收它的消息,但代理正在等待collatz
完成处理事件。
对此最简单,最愚蠢的解决方案是异步触发事件:
async { evt.Trigger i } |> Async.Start
这将确保事件处理程序不是“正确”执行,而是异步执行,可能在不同的线程上执行。这将允许代理在继续执行自己的执行循环之前不等待事件被处理。
通常,在处理多线程和异步时,不应该直接调用未知代码。代理不应该直接调用evt.Trigger
或其他任何它无法控制的东西,因为代码本身可能正在等待代理本身(这就是你的情况),从而引入了死锁。
以上是关于F#事件在Async工作流中不起作用的主要内容,如果未能解决你的问题,请参考以下文章
onRequestPermissionsResult 在片段中不起作用