[英]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
這是一個簡單的實驗,它重復創建一個函數來查找Collatz系列中的下一個數字,然后調用自身返回值,直到達到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())
由於您已達到問題的限制,我將在此處回答您的修改。
您在第二個代碼段中獲得超時的原因是您的代碼中存在死鎖。 讓我們跟蹤執行情況以查看它。
collatz
2 :第一次collatz
電話。 collatz
2 :第一個collatz
呼叫向座席發布消息。 collatz
呼叫。 collatz
1 :第二個collatz
呼叫向代理發布消息。 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.