我有一个邮箱处理器,它接收固定数量的邮件:
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
是否保证在发送的订单中接收和处理邮件?或者是否可能无序接收或处理消息?
答案 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附带的并发数据结构。