6

是否可以在其 MailboxProcessor 被释放(或以其他方式停止)时立即返回 PostAndAsyncReply?或者是否有一些关于如何安全使用 PostAndReply 方法而不产生死锁的“模式”/最佳实践?

现在我遇到的问题是 PostAndAsyncReply 在处理 MailboxProcessor 后永远不会返回。使用 timeout 参数不是一个选项,因为我不能等待(除了选择合理的超时是非常困难或不可能的,因为它取决于太多的因素)。

  [<Test>]
  let ``waiting for a reply from a disposed agent``() =
    use server = MailboxProcessor.Start(fun inbox -> async {
      ()
    })

    (server :> System.IDisposable).Dispose()

    server.PostAndReply (fun reply -> reply) // <- deadlock
    |> ignore)

编辑:我见过的大多数 MailboxProcessors 示例(包括 MSDN 上的示例)甚至不介意处置 MailboxProcessors。并且 MSDN 没有解释 MailboxProcessors 在被处理时如何反应。有没有必要丢弃它们?

4

1 回答 1

6

没有内置支持取消挂起的PostAndReply呼叫,但您可以实现这一点。要处理处置,您可以将邮箱的正文包裹在try .. finally. 在该finally块中,您可以以某种方式发出邮箱处理器已停止的信号。以下为此使用取消令牌源:

let disposed = new System.Threading.CancellationTokenSource()
let server = MailboxProcessor<AsyncReplyChannel<obj>>.Start(fun inbox -> 
  async { 
    try 
      // The normal body of the mailbox processor goes here
      do! Async.Sleep(1000)
      printfn "done"
    finally 
      // Cancel all pending calls post and reply
      disposed.Cancel()
   })

// Dispose the mailbox processor    
(server :> System.IDisposable).Dispose()

// When sending message, we use 'StartAsTask' and set a cancellation token,
// so that the work is stopped when the mailbox processor finishes
let wait = server.PostAndAsyncReply(fun reply -> reply)
let task = Async.StartAsTask(wait, cancellationToken = disposed.Token)
printfn "%A" task.Result

有两件事要记住:

  • 如果邮箱处理器正在运行一些长时间运行的工作(比如在上面休眠),那么处理将在完成后发生(这是因为异步工作流的性质 - 它们不会强制取消计算)

  • 我不得不在最后使用StartAsTask而不是RunSynchronously开始任务。由于某种原因(不太确定), usingRunSynchronously似乎并没有取消计算。

您可以轻松地将其包装在内部使用的某个类中MailboxProcessor,公开类似的接口并添加此功能-但这对于单个答案来说有点太多了!

要回答您关于 的问题Dispose,我不完全确定行为是什么,但处置邮箱处理器会处置用于表示消息已到达的内部AutoResetEvent(参见源代码)。我想这只是意味着邮箱处理器不会接受任何进一步的消息。

于 2013-08-19T13:23:59.463 回答