Reputation: 9805
When running the following code
open System
open Microsoft.FSharp.Control
type Message(id, contents) =
static let mutable count = 0
member this.ID = id
member this.Contents = contents
static member CreateMessage(contents) =
count <- count + 1
Message(count, contents)
let mailbox = new MailboxProcessor<Message>(fun inbox ->
let rec loop count =
async { printfn "Message count = %d. Waiting for next message." count
let! msg = inbox.Receive()
printfn "Message received. ID: %d Contents: %s" msg.ID msg.Contents
return! loop( count + 1) }
loop 0)
mailbox.Start()
mailbox.Post(Message.CreateMessage("ABC"))
mailbox.Post(Message.CreateMessage("XYZ"))
//System.Threading.Thread.Sleep(500)
Console.WriteLine("Press any key...")
Console.ReadLine() |> ignore
I get the following result
> Press any key...
Message count = 0. Waiting for next message.
Message received. ID: 1 Contents: ABC
Message count = 1. Waiting for next message.
Message received. ID: 2 Contents: XYZ
Message count = 2. Waiting for next message.
I would expect the msg press anykey to come somewhat after the first message...
And if I include the sleep, it indeed comes after.
So my question is :
Is the take away lesson of this that you can not expect any particular ordering when using async methods. aka, the code inside async can start with no specific precedence ?
Upvotes: 1
Views: 255
Reputation: 243041
As explained by John, the Post
method simply posts the message to a mailbox for later processing. The message may be processed before something else happens on the sender thread, but it may not - this simply depends on the thread scheduling and there is no control over that.
If you want to send message to a mailbox and wait for the result, you need to use PostAndReply
method (or, better use PostAndAsyncReply
from asynchronous workflow to avoid blocking). Here is an example:
/// The message carries AsyncReplyChannel<unit>, which is used to
/// notify the caller that the message was processed.
type Message =
| Message of int * string * AsyncReplyChannel<unit>
let mailbox = new MailboxProcessor<Message>(fun inbox ->
let rec loop count =
async { printfn "Message count = %d. Waiting for next message." count
// Receive & process the message
let! (Message(id, contents, repl)) = inbox.Receive()
printfn "Message received. ID: %d Contents: %s" msg.ID msg.Contents
// Notify the caller that processing has finished
repl.Reply()
return! loop( count + 1) }
loop 0)
mailbox.Start()
// Send message and wait for reply using 'PostAndReply' method
mailbox.PostAndReply(fun chnl -> Message(0, "ABC", chnl))
mailbox.PostAndReply(fun chnl -> Message(0, "XYZ", chnl))
Upvotes: 5
Reputation: 25516
From the docs for mailboxProcessor
(where this code sample is from)
Post
Posts a message to the message queue of the MailboxProcessor, asynchronously.
Note - Post
makes no gurantees about processing - that is the whole idea behind async. If you need to wait for the computation to finish you need to use PostAndReply
- although at this point you lose some of the benefit of multithreading.
The MailboxProcessor
will always process the messages in order, but unless you wait for it, the messages won't have finished processing
Upvotes: 5