nicolas
nicolas

Reputation: 9805

async behaviour in Fsharp

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

Answers (2)

Tomas Petricek
Tomas Petricek

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

John Palmer
John Palmer

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

Related Questions