处置(或以其他方式停止)其MailboxProcessor时,是否可以使PostAndAsyncReply立即返回?还是有一些关于如何安全使用PostAndReply方法而不创建死锁的“模式”/最佳实践?

现在,我有一个问题,当处理MailboxProcessor时,PostAndAsyncReply永远不会返回。使用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在被处置时的 react 。是否有必要处置它们?

最佳答案

没有取消挂起的PostAndReply调用的内置支持,但是您可以实现此功能。要处理,可以将邮箱正文包裹在try .. finally中。在finally块中,您可以以某种方式表明邮箱处理器已停止。以下为此使用了取消 token 源:

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

有两件事要牢记:
  • 如果邮箱处理器正在运行一些长时间运行的工作(如上面的 sleep ),则将在完成后进行处理(这是由于异步工作流的性质-它们不会强制取消计算)
  • 我必须使用StartAsTask而不是RunSynchronously才能在最后开始任务。由于某种原因(不太确定),使用RunSynchronously似乎无法取消计算。

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

    为了回答有关Dispose的问题,我不完全确定行为是什么,但是配置邮箱处理器将配置内部AutoResetEvent(请参阅the source code),该信号用于指示消息已到达。我想这只是意味着邮箱处理器将不接受任何其他消息。

    10-06 10:11
    查看更多