简体   繁体   English

F#事件在Async工作流中不起作用

[英]F# events not working inside Async workflow

I want to do a Post-Fire-Reply to an agent. 我想对代理人进行Post-Fire-Reply。 Basically the agent triggers an event then replies to the caller. 基本上,代理触发事件然后回复调用者。 However I either keep getting a timeout error or the events do not fire correctly. 但是,我要么继续收到超时错误,要么事件无法正确触发。 I tried doing Post-Fire, that stopped the timeout errors but the events do not fire. 我尝试过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    

This is a simple experiment that repeatedly creates a function to find the next number in the Collatz series and then calls itself to return the value until it reaches 1. 这是一个简单的实验,它重复创建一个函数来查找Collat​​z系列中的下一个数字,然后调用自身返回值,直到达到1。

What seems to happen is that the trigger only fires once. 似乎发生的事情是触发器只触发一次。 I tried experimenting with every combination of Async.RunSynchronously / Async.Start / StartChild / SynchronizationContext that I could think of but no progress. 我尝试了Async.RunSynchronously / Async.Start / StartChild / SynchronizationContext的每个组合,我能想到但没有进展。 I found a blog similar to what I am doing but that didn't help me neither 我发现了一个类似于我正在做的博客 ,但这对我没有帮助

EDIT Thank you Fyodor Soikin for pointing out my oversight. 编辑感谢Fyodor Soikin指出我的疏忽。 The original problem still remains in that I wish to both fire events and reply with a result, but get a timeout. 最初的问题仍然存在,我希望同时触发事件并回复结果,但是会超时。

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

Your loop function doesn't loop. 你的loop函数不循环。 It receives the first message, triggers the event, and then just... exits. 它接收第一条消息,触发事件,然后只是...退出。 Never attempts to receive a second message. 永远不要尝试接收第二条消息。

You need to make that function work continuously: process the first message, then go right back to receive the next one, then go receive the next one, and so on. 您需要使该功能连续工作:处理第一条消息,然后返回接收下一条消息,然后接收下一条消息,依此类推。 Like this: 像这样:

let agent = Agent.Start(fun inbox -> 
    let rec loop() = async {
        let! msg = inbox.Receive()
        let (Fire i) = msg
        evt.Trigger i
        return! loop() }
    loop())

Edit 编辑

Since you've reached your limit on questions, I will answer your edit here. 由于您已达到问题的限制,我将在此处回答您的修改。

The reason you're getting timeouts in your second snippet is that you have a deadlock in your code. 您在第二个代码段中获得超时的原因是您的代码中存在死锁。 Let's trace the execution to see that. 让我们跟踪执行情况以查看它。

  1. THREAD 1 : The agent is started. 线程1 :代理启动。
  2. THREAD 2 : The first collatz call. collatz 2 :第一次collatz电话。
  3. THREAD 2 : The first collatz call posts a message to the agent. collatz 2 :第一个collatz呼叫向座席发布消息。
  4. THREAD 1 : The agent receives the message. 线程1 :代理接收消息。
  5. THREAD 1 : The agent triggers the event. 线程1 :代理触发事件。
  6. THREAD 1 : As a result of the event, the second collatz call happens. 线程1 :由于事件的结果,发生了第二次的collatz呼叫。
  7. THREAD 1 : The second collatz call posts a message to the agent. collatz 1 :第二个collatz呼叫向代理发布消息。
  8. THREAD 1 : The second collatz call starts waiting for the agent to respond. collatz 1 :第二个collatz呼叫开始等待代理响应。

And this is where the execution ends. 这就是执行结束的地方。 The agent cannot respond at this point (in fact, it cannot even receive the next message!), because its instruction pointer is still inside evt.Trigger . 代理程序此时无法响应(事实上,它甚至无法接收下一条消息!),因为它的指令指针仍在evt.Trigger The evt.Trigger call hasn't yet returned, so the loop function hasn't yet recursed, so the inbox.Receive function hasn't yet been called, so the second message is still waiting in the agent's queue. evt.Trigger调用尚未返回,因此loop函数尚未递归,因此尚未调用inbox.Receive函数,因此第二条消息仍在代理队列中等待。

So you get yourself a classic deadlock: collatz is waiting for the agent to receive its message, but the agent is waiting for collatz to finish handling the event. 因此,您将获得一个经典的死锁: collatz正在等待代理接收其消息,但代理正在等待collatz完成处理事件。

The simplest, dumbest solution to this would be to just trigger the event asynchronously: 对此最简单,最愚蠢的解决方案是异步触发事件:

    async { evt.Trigger i } |> Async.Start

This will make sure that the event handler is executed not "right there", but asynchronously, possibly on a different thread. 这将确保事件处理程序不是“正确”执行,而是异步执行,可能在不同的线程上执行。 This will in turn allow the agent not to wait for the event to be processed before it can continue its own execution loop. 这将允许代理在继续执行自己的执行循环之前不等待事件被处理。

In general though, when dealing with multithreading and asynchrony, one should never call unknown code directly. 通常,在处理多线程和异步时,不应该直接调用未知代码。 The agent should never directly call evt.Trigger , or anything else that it doesn't control, because that code might be waiting on the agent itself (which is what happened in your case), thus introducing the deadlock. 代理不应该直接调用evt.Trigger或其他任何它无法控制的东西,因为代码本身可能正在等待代理本身(这就是你的情况),从而引入了死锁。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM