保证邮件发送到邮箱处理器的邮件顺序

时间:2010-02-08 05:03:55

标签: concurrency f#

我有一个邮箱处理器,它接收固定数量的邮件:

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")

这些消息应该按照发送的顺序处理。在我的测试过程中,它会打印出它应该准确的内容:

First message
Second message
Third message

但是,由于邮件发布是异步的,因此听起来快速发布3条消息可能会导致按任何顺序处理项目。例如,我想要不按顺序接收消息并得到这样的信息:

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

是否保证在发送的订单中接收和处理邮件?或者是否可能无序接收或处理消息?

1 个答案:

答案 0 :(得分:8)

由于F#的异步工作流程的工作方式,consumeThreeMessages函数中的代码将始终按顺序执行。

以下代码:

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

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

        }

大致翻译为:

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

当您查看desugared表单时,很明显代码是串行执行的。 “异步”部分在async.Bind的实现中发挥作用,它将异步启动计算并在完成执行时“唤醒”。这样您就可以利用异步硬件操作,而不是在等待IO操作的OS线程上浪费时间。

但这并不意味着在使用F#的异步工作流时不会遇到并发问题。想象一下,你做了以下事情:

let total = ref 0

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

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

上面的代码将有两个异步工作流同时修改相同的状态。

因此,简要回答一下您的问题:在单个异步块的主体内,事物将始终按顺序执行。 (也就是说,let!或do!之后的下一行在异步操作完成之前不会执行。)但是,如果你在两个异步任务之间共享状态,那么所有的赌注都会关闭。在这种情况下,您需要考虑锁定或使用CLR 4.0附带的并发数据结构。