Is it possible to make PostAndAsyncReply return immediately when its MailboxProcessor is disposed (or otherwise stopped)? Or is there some "pattern" / best practice on how to safely use PostAndReply methods without creating deadlocks?
Right now I have the problem that PostAndAsyncReply never returns when the MailboxProcessor has been disposed. Using the timeout parameter isn't an option since I cannot wait (besides that choosing reasonable timeouts is quite hard or impossible anyway since it depends on way too many factors).
[<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)
edit: Most examples with MailboxProcessors I have seen (incl. the ones on MSDN) don't even mind disposing the MailboxProcessors. And the MSDN doesn't explain how MailboxProcessors react when being disposed. Is it unnecessary to dispose them?
There is no built-in support for the cancellation of pending PostAndReply
calls, but you can implement this. To handle for disposal, you can wrap the body of the mailbox in try .. finally
. In the finally
block, you can somehow signal that the mailbox processor has stopped. The following uses cancellation token source for this:
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
There are two things to keep in mind:
If the mailbox processor is running some long-running work (like sleeping above), then the disposal will happen after this is completed (this is because of the nature of async workflows - they do not forcefully cancel computations)
I had to use StartAsTask
instead of RunSynchronously
to start the task at the end. For some reason (not quite sure), using RunSynchronously
does not seem to cancel the computation.
You could easily wrap this in some class that uses MailboxProcessor
internally, exposes similar interface and adds this functionality - but that's a bit too much for a single answer!
To answer your question regarding Dispose
, I'm not entirely sure what the behaviour is, but disposing the mailbox processor disposes an internal AutoResetEvent
(see the source code) that is used to signal that a message has arrived. I suppose this just means that the mailbox processor will not accept any further messages.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With