I wonder, why MailboxProcessor
's default strategy of handling exceptions is just silently ignore them. For example:
let counter =
MailboxProcessor.Start(fun inbox ->
let rec loop() =
async { printfn "waiting for data..."
let! data = inbox.Receive()
failwith "fail" // simulate throwing of an exception
printfn "Got: %d" data
return! loop()
}
loop ())
()
counter.Post(42)
counter.Post(43)
counter.Post(44)
Async.Sleep 1000 |> Async.RunSynchronously
and nothing happens. There is no fatal stop of the program execution, or message box with "An unhandled exception" arises. Nothing.
This situation becomes worse if someone uses PostAndReply
method: a guaranteed deadlock as the result.
Any reasons for such behavior?
There is an Error
event on the MailboxProcessor.
http://msdn.microsoft.com/en-us/library/ee340481
counter.Error.Add(fun e -> printfn "%A" e)
Of course, you can do something like Tomas' solution if you want to exert fine control yourself.
I think the reason why the MailboxProcessor
in F# does not contain any mechanism for handling exceptions is that it is not clear what is the best way for doing that. For example, you may want to have a global event that is triggered when an unhandled exception happens, but you may want to rethrow the exception on the next call to Post
or PostAndReply
.
Both of the options can be implemented based on the standard MailboxProcessor
, so it is possible to add the behaviour you want. For example, the following snippet shows HandlingMailbox
that adds a global exception handler. It has the same interface as normal MailboxProcessor
(I omitted some methods), but it adds OnError
event that is triggered when an exception happens:
type HandlingMailbox<'T> private(f:HandlingMailbox<'T> -> Async<unit>) as self =
let event = Event<_>()
let inbox = new MailboxProcessor<_>(fun inbox -> async {
try
return! f self
with e ->
event.Trigger(e) })
member x.OnError = event.Publish
member x.Start() = inbox.Start()
member x.Receive() = inbox.Receive()
member x.Post(v:'T) = inbox.Post(v)
static member Start(f) =
let mbox = new HandlingMailbox<_>(f)
mbox.Start()
mbox
To use it, you would write the same code as what you wrote before, but you can now handle exceptions asynchronously:
let counter = HandlingMailbox<_>.Start(fun inbox -> async {
while true do
printfn "waiting for data..."
let! data = inbox.Receive()
failwith "fail" })
counter.OnError.Add(printfn "Exception: %A")
counter.Post(42)
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