给定2个代理,通过发送消息进行通信:
代理1向代理2发送
PostAndAsyncReply
,等待结果。代理2开始处理消息,但抛出异常。
现在代理1已死锁,因为代理1没有发送任何回复。正如我所看到的,有两种方法可以处理这种情况:
我可以为timeout
设置PostAndAsyncReply
,但由于操作可能会长时间运行,因此执行所需的时间会有所不同。
将代理2中的代码包装在try..catch
中,然后回复一些表示错误的值。但是,这意味着所有使用ReplyChannel处理消息的代码都需要包含在try..catch
内,这看起来很麻烦。
如何处理这种情况?
答案 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
因某种自然原因而被终止,但他的伙伴并不知道。
因此,恕我直言,缺乏回应是代理人的自然事情,接收者必须准备好处理它。所以,我只想在这件事情上正确告知他们。我这样做了:
我对操作长度做了一些估算,并使用PostAndTryAsyncReply
进行了这些估算
如果超时,我给出了一个特殊的返回案例:NotResponding
。在这种情况下,代理可以决定做什么。
以下是一个例子:
// 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
请注意,您可以进一步扩展此方法,例如:
让操作有机会估计其预期执行时间
更好地跟踪代理商的状态并提供更详细的回复
<强> 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 ()
}
所以,在这里,我在代理初始化中插入了额外的错误处理,以便能够通知用户该代理已经关闭。