PostAndAsyncReply异常死锁

时间:2014-04-14 08:54:29

标签: .net f#

给定2个代理,通过发送消息进行通信:

  

代理1向代理2发送PostAndAsyncReply,等待结果。

     

代理2开始处理消息,但抛出异常。

现在代理1已死锁,因为代理1没有发送任何回复。正如我所看到的,有两种方法可以处理这种情况:

  1. 我可以为timeout设置PostAndAsyncReply,但由于操作可能会长时间运行,因此执行所需的时间会有所不同。

  2. 将代理2中的代码包装在try..catch中,然后回复一些表示错误的值。但是,这意味着所有使用ReplyChannel处理消息的代码都需要包含在try..catch内,这看起来很麻烦。

  3. 如何处理这种情况?

2 个答案:

答案 0 :(得分:0)

你总是可以使用更高阶的函数来稍微减轻try...catch

let replyOrFail (chan: AsyncReplyChannel<option<'T>>) (action: Async<'T>) : Async<unit> =
    async {
        try
            let! reply = action
            return chan.Reply (Some reply)
        with _ ->
            return chan.Reply None
    }

let agent2 =
    (* declare mailbox... *)
    (* matching received message: *)
    | MessageFromAgent1 chan ->
        do! replyOrFail chan <| async {
            return! longComputationThatMightThrow()
        }
    (* ... *)

答案 1 :(得分:0)

我想提一下,例外不是Agent缺乏回应的唯一情况。另一个原因可能是Agent因某种自然原因而被终止,但他的伙伴并不知道。

因此,恕我直言,缺乏回应是代理人的自然事情,接收者必须准备好处理它。所以,我只想在这件事情上正确告知他们。我这样做了:

  1. 我对操作长度做了一些估算,并使用PostAndTryAsyncReply进行了这些估算

  2. 如果超时,我给出了一个特殊的返回案例:NotResponding。在这种情况下,代理可以决定做什么。

  3. 以下是一个例子:

    // outside of the agent
    type Timeouts = {
        load : int
        connect : int
        agent : int
        awaiter : int
    }
    
    let timeouts = { load = 5000; connect = 2000; agent = 1000; awaiter = 100 }
    
    // a static member in a class that wraps my agent
    let tryCommand command timeout = async {
        let! answer = a.PostAndTryAsyncReply (command, timeout)
        match answer with
        | Some state -> return State state
        | None -> return NotResponding
    }        
    
    // regular members of the wrapper
    member x.Connect () = tryCommand Commands.Connect (timeouts.connect + timeouts.agent)
    member x.Reload () = tryCommand Commands.Reload (timeouts.load + timeouts.agent)
    member x.Close () = tryCommand Commands.Close timeouts.agent
    

    请注意,您可以进一步扩展此方法,例如:

    1. 让操作有机会估计其预期执行时间

    2. 更好地跟踪代理商的状态并提供更详细的回复

    3. <强> PS 即可。这个想法尚未经过测试,但看起来很有希望:

      所有代理商都有类似的模式:

      let agent = MailboxProcessor.Start(fun inbox ->
          let rec initialState () = 
              async {
              }
          and anotherState () = 
              async {
              }
          // and so on
      
          initialState () // simply run it
      

      我们可以尝试这样写:

      let agent = MailboxProcessor.Start(fun inbox ->
          let rec initialState () = 
              async {
              }
          and anotherState () = 
              async {
              }
          and errorState e = 
              async {
                   // informing on error
              }
          // and so on
      
          async { // instead of just initialState ()
              let! res = initialState () |> Async.Catch
              match res with
              | Choice2Of2 e -> return! errorState ()
              | _ -> return ()
          }
      

      所以,在这里,我在代理初始化中插入了额外的错误处理,以便能够通知用户该代理已经关闭。