Asynchronous F#事件在异步工作流中不起作用

Asynchronous F#事件在异步工作流中不起作用,asynchronous,events,f#,agents,mailboxprocessor,Asynchronous,Events,F#,Agents,Mailboxprocessor,我想在火灾后回复一位特工。基本上,代理会触发一个事件,然后回复调用者。但是,我要么不断收到超时错误,要么事件无法正确触发。我尝试过执行Post Fire,它停止了超时错误,但事件没有触发 let evt = new Event<int>() let stream = evt.Publish type Agent<'T> = MailboxProcessor<'T> type Fire = Fire of int let agent = Agent.Star

我想在火灾后回复一位特工。基本上,代理会触发一个事件,然后回复调用者。但是,我要么不断收到超时错误,要么事件无法正确触发。我尝试过执行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    
let evt=new Event()
让stream=evt.Publish
类型代理
火灾类型=内部火灾
让agent=agent.Start(有趣的收件箱->
让rec loop()=异步{
let!msg=inbox.Receive()
放(火)=味精
evt.Trigger i}
循环())
让我来看看fn=
流动
|>Observable.filter(乐趣x->x=i)
|>Observable.filter(乐趣x->x 1)
|>Observable.subscribe(乐趣x->fn x)
让我来记录一下=
printfn“%d”n
关于n(乐趣i->
如果(i%2=0),则collatz(i/2)
else-collatz(3*n+1))|>忽略
agent.Post(Fire n)//这不起作用
//evt.Trigger n//这确实有效
科拉茨13
这是一个简单的实验,重复创建一个函数来查找Collatz系列中的下一个数字,然后调用自身返回值,直到它达到1为止

似乎发生的是触发器只触发一次。我尝试尝试了Async.RunSynchronously/Async.Start/StartChild/SynchronizationContext的每一种组合,但没有任何进展。我发现了一个类似于我现在所做的事情,但这对我也没有帮助

编辑 谢谢菲奥多·索金指出我的疏忽。最初的问题仍然存在,我希望触发事件并返回结果,但得到一个超时

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
let evt=new Event()
让stream=evt.Publish
类型代理
类型命令=
|内特之火
|获取int*AsyncReplyChannel
让agent=agent.Start(有趣的收件箱->
让rec loop()=异步{
let!msg=inbox.Receive()
配味精
|点火i->evt.触发i
|获取(i,ch)->
evt.触发器i
总答覆(i)
return!loop()}
循环())
让我来看看fn=
流动
|>Observable.filter(乐趣x->x=i)
|>Observable.filter(乐趣x->x 1)
|>Observable.subscribe(乐趣x->fn x)
让我来记录一下=
printfn“%d”n
关于n(乐趣i->
如果(i%2=0),则collatz(i/2)
else-collatz(3*n+1))|>忽略
agent.PostAndReply(fun ch->(Get(n,ch))|>忽略//超时
agent.PostAndAsyncReply(fun ch->(Get(n,ch)))|>Async.Ignore |>Async.Start//可以工作,但我需要结果
agent.PostAndAsyncReply(fun ch->(Get(n,ch))|>Async.RunSynchronously |>ignore//timeout
科拉茨13

您的
循环
函数不循环。它接收第一条消息,触发事件,然后。。。出口。永远不要尝试接收第二条消息

您需要使该功能持续工作:处理第一条消息,然后返回接收下一条消息,然后继续接收下一条消息,依此类推。像这样:

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
    ,或者它无法控制的任何东西,因为代码可能正在等待代理本身(在您的案例中就是这样),从而引入死锁。

    Grr。当我在实验中复制/粘贴时,我一定错过了这一点。我将尝试看看这是否有帮助,但这最初是在我的代码中。如果没有
    do,您可能已经有了
    loop()
    做!不应用于递归调用,因为它会泄漏内存。使用返回!相反谢谢你的提示,解决了这个问题!我盯着这个看太久了。不幸的是,它没有解决我原来的问题。查看我的编辑了解详细信息。好的,既然你已经达到了极限,我已经在这里回答了这个问题。不过,在将来,请不要用你的问题创造一个重复的对话。这与SO的目的背道而驰,SO应该是c