簡體   English   中英

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

[英]F# events not working inside Async workflow

我想對代理人進行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    

這是一個簡單的實驗,它重復創建一個函數來查找Collat​​z系列中的下一個數字,然后調用自身返回值,直到達到1。

似乎發生的事情是觸發器只觸發一次。 我嘗試了Async.RunSynchronously / Async.Start / StartChild / SynchronizationContext的每個組合,我能想到但沒有進展。 我發現了一個類似於我正在做的博客 ,但這對我沒有幫助

編輯感謝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. 線程1 :代理啟動。
  2. collatz 2 :第一次collatz電話。
  3. collatz 2 :第一個collatz呼叫向座席發布消息。
  4. 線程1 :代理接收消息。
  5. 線程1 :代理觸發事件。
  6. 線程1 :由於事件的結果,發生了第二次的collatz呼叫。
  7. collatz 1 :第二個collatz呼叫向代理發布消息。
  8. collatz 1 :第二個collatz呼叫開始等待代理響應。

這就是執行結束的地方。 代理程序此時無法響應(事實上,它甚至無法接收下一條消息!),因為它的指令指針仍在evt.Trigger evt.Trigger調用尚未返回,因此loop函數尚未遞歸,因此尚未調用inbox.Receive函數,因此第二條消息仍在代理隊列中等待。

因此,您將獲得一個經典的死鎖: collatz正在等待代理接收其消息,但代理正在等待collatz完成處理事件。

對此最簡單,最愚蠢的解決方案是異步觸發事件:

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

這將確保事件處理程序不是“正確”執行,而是異步執行,可能在不同的線程上執行。 這將允許代理在繼續執行自己的執行循環之前不等待事件被處理。

通常,在處理多線程和異步時,不應該直接調用未知代碼。 代理不應該直接調用evt.Trigger或其他任何它無法控制的東西,因為代碼本身可能正在等待代理本身(這就是你的情況),從而引入了死鎖。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM