我试图通过MailboxProcessor<'Msg>类开始使用F#中的代理,但我很快意识到我没有对异常进行适当的处理。在Haskellian的世界中,不会有任何例外,因此处理问题的正确方法是简单地将它们作为响应案例提供;因此,代理可以使用以下内容进行回复:
type AgentResponse =
| HandledData of string
| InvalidData of string然后,可以调用代理的.PostAndReply方法,并获得一个包含指示数据无效原因的消息的InvalidData。然而,这不是Haskell,有时会发生异常。所以如果我这样做:
let agent =
new MailboxProcessor<string * AsyncReplyChannel<AgentResponse>>(fun inbox ->
async {
while true do
let! (msg, replyChannel) = inbox.Receive()
if msg = "die" then failwith "Unknown exception encountered!"
else replyChannel.Reply(HandledData(msg))
})
agent.Start()
agent.PostAndReply (fun rc -> "die", rc)
agent.PostAndReply (fun rc -> "Test", rc)对agent.PostAndReply的第二个调用会无限期地阻塞。当不使用AsyncReplyChannel并因此只调用agent.Post时,调用不会阻塞,但是一旦代理遇到异常,新消息就会留在队列中。在这两种情况下,重新启动代理似乎都是不可能的,因为agent.Start函数在再次调用时会返回一个InvalidOperationException,处理这种情况的自然方法似乎是创建一个状态干净的新代理,但随后所有排队的消息都会丢失。
除了在try..with中包装整个代理之外,还有什么好方法可以让代理在出现异常后继续运行?或者,是否已经建立了一种“标准”的方法来处理这个问题,有人可以给我指点?
发布于 2015-01-02 23:55:16
您在Haskell中确实有异常:在ghci中尝试Data.List.head [] ...
不幸的是,缺乏依赖类型意味着,在Haskell或F#中,我们可以编写没有计算意义的类型正确的代码。
实际上,用try ... with块包装来处理异常并不是一个坏主意。您不需要包装整个正文,但只需要包装代码的非纯部分。
然后,按照传统方法,返回使用适当的构造函数生成的值。
发布于 2015-01-03 00:00:44
一种可能的选择是一种类似于this question的Tomas Petricek定义的HandlingMailbox的助手类型,它重复运行代理的主体:
type ResilientMailbox<'T> private(f:ResilientMailbox<'T> -> Async<unit>) as self =
let event = Event<_>()
let inbox = new MailboxProcessor<_>(fun _inbox ->
let rec loop() = async {
try
return! f self
with e ->
event.Trigger(e)
return! loop()
}
loop())
member __.OnError = event.Publish
member __.Start() = inbox.Start()
member __.Receive() = inbox.Receive()
member __.Post(v:'T) = inbox.Post(v)
static member Start(f) =
let mbox = new ResilientMailbox<_>(f)
mbox.Start()
mbox它可以像普通的MailboxProcessor一样启动和运行,但如果提供的代理主体抛出异常,它将重新运行自己。
编辑:更改内部MailboxProcessor以使用递归函数而不是while true do..块;当目标函数正常返回时,前面的代码不会停止运行。
https://stackoverflow.com/questions/27744533
复制相似问题