Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Guarantee order of messages posted to mailbox processor

Tags:

concurrency

f#

I have a mailbox processor which receives a fixed number of messages:

let consumeThreeMessages = MailboxProcessor.Start(fun inbox ->
        async {
            let! msg1 = inbox.Receive()
            printfn "msg1: %s" msg1

            let! msg2 = inbox.Receive()
            printfn "msg2: %s" msg2

            let! msg3 = inbox.Receive()
            printfn "msg3: %s" msg3
        }
    )

consumeThreeMessages.Post("First message")
consumeThreeMessages.Post("Second message")
consumeThreeMessages.Post("Third message")

These messages should be handled in exactly the order sent. During my testing, it prints out exactly what it should:

First message
Second message
Third message

However, since message posting is asynchronous, it sounds like posting 3 messages rapidly could result in items being processed in any order. For example, I do not want to receive messages out of order and get something like this:

Second message // <-- oh noes!
First message
Third message 

Are messages guaranteed to be received and processed in the order sent? Or is it possible for messages to be received or processed out of order?

like image 577
Juliet Avatar asked Feb 08 '10 05:02

Juliet


1 Answers

The code in your consumeThreeMessages function will always execute in order, because of the way F#'s async workflows work.

The following code:

   async {
            let! msg1 = inbox.Receive()
            printfn "msg1: %s" msg1

            let! msg2 = inbox.Receive()
            printfn "msg2: %s" msg2

        }

Roughly translates to:

async.Bind(
    inbox.Receive(),
    (fun msg1 -> 
         printfn "msg1: %s" msg1
         async.Bind(
             inbox.Receive(),
             (fun msg2 -> printfn "msg2: %s" msg2)
         )
    )
)

When you look at the desugared form, it is clear that the code executes in serial. The 'async' part comes into play in the implementation of async.Bind, which will start the computation asynchronously and 'wake up' when it completes to finish the execution. This way you can take advantage of asynchronous hardware operations, and not waste time on OS threads waiting for IO operations.

That doesn't mean that you can't run into concurrency issues when using F#'s async workflows however. Imagine that you did the following:

let total = ref 0

let doTaskAsync() = 
    async { 
        for i = 0 to 1000 do 
            incr total
    } |> Async.Start()

// Start the task twice
doTaskAsync()
doTaskAsync()

The above code will have two asynchronous workflows modifying the same state at the same time.

So, to answer your question in brief: within the body of a single async block things will always execute in order. (That is, the next line after a let! or do! doesn't execute until the async operation completes.) However, if you share state between two async tasks, then all bets are off. In that case you will need to consider locking or using Concurrent Data Structures that come with CLR 4.0.

like image 103
Chris Smith Avatar answered Oct 26 '22 16:10

Chris Smith