Can an F # agent with multiple recursive asynchronous bodies use multiple inbox.Receive () in each?
I have a multi-state F # MailboxProcessor example, just wondering why it compiles, but the behavior is unexpected - can F # agents only have one inbox.Receive () statement in a passed in lambda function? I am trying to follow the generic example pattern provided in "Expert F # 3.0" on page 284 where using multiple async bodies {} allows multiple states, but is not specific as to whether inbox.Receive () can be used in each async mode?
open System
let mb1<'T> = MailboxProcessor<string>.Start(fun inbox ->
let rec loop1 (n:int) = async {
printfn "loop1 entry "
let! msg = inbox.Receive()
do! Async.Sleep(1000)
printfn "loop1 calling loop2" //msg received %A" msg
return! loop2 (n+1) }
and loop2 (x:int) = async {
printfn "loop2 entry"
let! msg2 = inbox.Receive()
printfn "loop2 msg received %A" msg2
printfn "loop2 calling loop1"
return! loop1 (x+1) }
loop2 0
)
mb1.Post("data message 1")
mb1.Post("data message 2")
gives
loop2 entry
loop2 msg received "data message 1"
loop2 calling loop1
loop1 entry
val it : unit = ()
>
loop2 entry
loop2 msg received "data message 2"
loop2 calling loop1
loop1 entry
val it : unit = ()
>
so let it be! msg = inbox.Receive () is skipped on loop 1? I would have thought that loop2 ended up returning! loop1 and what let! the purpose of inbox.Receive () is specific to the async block in which it was used.
source to share
This is due to the so-called " value limitation ".
Since you gave mb1
an explicit generic parameter, it compiles as a function, not a value. Without going into details, the compiler should do this in order to make it easier to access the value using different generic arguments.
As a result, every time you reference mb1
, there is actually a function call that creates a completely new agent, so the two calls .Post
are on different objects.
To remedy the situation, either remove the common argument from the value, thereby making it a real computed value:
let mb1 = MailboxProcessor<string>( ...
Or make sure you only call it once:
let mb = mb1
mb.Post("data message 1")
mb.Post("data message 2")
source to share