我正在尝试通过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
之外,有没有什么好方法可以让代理在异常后继续运行?或者,有一种“标准”的处理方式,有人可以指出我吗?
答案 0 :(得分:3)
你在Haskell中有例外:在ghci中尝试Data.List.head []
....
不幸的是,在Haskell或F#中,缺少依赖类型意味着我们可以编写没有计算意义的类型正确的代码。
实际上,使用try ... with
块包装对于处理异常并不是一个坏主意。你不需要包裹整个主体,只需要包含代码的非纯粹部分。
然后经典地,您回退使用适当的构造函数创建的值。
答案 1 :(得分:2)
一种可能的选择是类似于Tomas Petricek在this question定义的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..
块;当目标函数正常返回时,前面的代码不会停止运行。