【问题标题】:Restart MailboxProcessor after failure?失败后重启MailboxProcessor?
【发布时间】:2015-01-02 15:35:25
【问题描述】:

我正在尝试通过 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 中之外,还有什么好方法可以让代理在异常后继续运行?或者,是否已经建立了一种“标准”的方式来处理这个问题,有人可以指点我吗?

【问题讨论】:

  • 我认为MailboxProcessor(代理)的整个想法是它会因失败而死亡。也许您可以使用另一个代理作为队列,然后在出现错误时使用TryPostAndReply 队列代理启动一个新代理?如果您需要高性能,我认为try...catch 可能是最好的选择。
  • @mydogisbox 仅仅捕捉一切似乎不是一个最佳的想法,但我同意这似乎也是最好的方法。我希望避免的问题是必须保留排队消息的两份副本;例如,如果我在 F# 中编写了一个邮件程序守护程序。守护进程不应该仅仅因为代理遇到我没想到的网络异常而“丢失”排队的电子邮件,因此它需要将消息保留在其他地方以及队列中。如果我需要所述队列中所有内容的第二个副本,感觉就像它破坏了代理拥有队列的目的。
  • 我同意。我想知道 erlang 代理模型是如何处理这个问题的。

标签: asynchronous f# mailboxprocessor


【解决方案1】:

在 Haskell 中确实有例外:在 ghci 中尝试 Data.List.head []....

不幸的是,缺少依赖类型意味着,在 Haskell 或 F# 中,我们可以编写类型正确的代码,但没有计算意义。

实际上,用try ... with 块包裹起来对于处理异常并不是一个坏主意。您不需要包裹整个主体,只需包裹代码的非纯部分即可。

然后,按照传统方式,您返回一个使用适当构造函数生成的值。

【讨论】:

  • “只是代码的非纯部分”在 .NET 中,难道不是全部吗? :) 等待并希望 pure 关键字会发生:fslang.uservoice.com/forums/245727-f-language/suggestions/…
  • 也许,但未强制执行纯代码;到那时,为什么会有像 Haskell 这样的语言呢?此外,很难确定 BCL(和第三方库)中的哪些对象可以安全地以纯方式使用。
  • @nicolas :一个相当不公平的比较,因为 C 没有异常(忽略浮点信号),而 90% 的 BCL 方法可能会抛出异常。
【解决方案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.. 块;目标函数正常返回时,前面的代码不会停止运行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-06
    • 1970-01-01
    • 1970-01-01
    • 2015-01-08
    • 2020-05-21
    • 2022-01-15
    • 2021-01-13
    • 2020-07-05
    相关资源
    最近更新 更多