Developer11
Developer11

Reputation: 781

Wait on the mailboxprocessor

Is it possible to wait on the mailboxprocessor, following code works in F# interactive but is there a way to wait on it in an application or a unit test?

[<TestMethod>]
member this.TestMailboxProcessor() =
    let mailboxProcessor = MailboxProcessor<string>.Start(fun inbox ->
        async {
            while true do
            let! msg = inbox.Receive()
            printfn "agent got message %s" msg // too late, UnitTest exits
        }
    )

    mailboxProcessor.Post "ping"
    Console.WriteLine "message posted" // I see this in the console
    Assert.IsTrue(true)

Upvotes: 5

Views: 647

Answers (2)

Honza Brestan
Honza Brestan

Reputation: 10957

It's not possible in exactly this scenario, but you can define your message type to include an AsyncReplyChannel<'t>, which then allows you to use MailboxProcessor.PostAndReply instead of Post. This way the calling code can (either synchronously or asynchronously) wait for a response value, or at least an indication that the processing is done.

Your modified source code may look like this:

[<TestMethod>]
member this.TestMailboxProcessor() =
    let mailboxProcessor =
        MailboxProcessor<string * AsyncReplyChannel<unit>>.Start(fun inbox ->
            async {
                while true do
                let! msg, replyChannel = inbox.Receive()
                printfn "agent got message %s" msg
                (* 
                  Reply takes a value of the generic param of
                  AsyncReplyChannel<'t>, in this case just a unit
                *)
                replyChannel.Reply()
            }
        )

    (*
      You can't create an AsyncReplyChannel<'t> value, but this does it for you.
      Also always, always use timeouts when awaiting message replies. 
    *)
    mailboxProcessor.PostAndReply(
        (fun replyChannel -> "ping", replyChannel),
        timeout = 1000)
    (* This gets printed only after the message has been posted and processed *)
    Console.WriteLine "message posted"
    Assert.IsTrue(true)

MailboxProcessors are a bit tricky topic though, so make sure you always use timeouts, otherwise in case of errors in your code, or exceptions killing the message loop, your code would hang forever. Not good in tests, even worse in production.

Upvotes: 6

Szer
Szer

Reputation: 3476

You should use PostAndAsyncReply or PostAndReply (blocking version)

let replyAgent = MailboxProcessor.Start(fun inbox ->
    let rec loop() = 
        async {
            let! (replyChannel: AsyncReplyChannel<_>), msg = inbox.Receive()
            replyChannel.Reply (sprintf "replied for message: %A" msg)
            return! loop()
        }
    loop() )

let reply = replyAgent.PostAndReply(fun replCh -> replCh, "Hi")
printfn "%s" reply //prints "replied for message: Hi"

Upvotes: 3

Related Questions