失败后重启MailboxProcessor?

时间:2015-01-02 15:35:25

标签: asynchronous f# mailboxprocessor

我正在尝试通过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之外,有没有什么好方法可以让代理在异常后继续运行?或者,有一种“标准”的处理方式,有人可以指出我吗?

2 个答案:

答案 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..块;当目标函数正常返回时,前面的代码不会停止运行。