I was trying to find an example about how to use TryScan
, but haven't found any, could you help me?
What I would like to do (quite simplified example): I have a MailboxProcessor
that accepts
two types of mesages.
First one
GetState
returns current state.GetState
messages are sent quite frequentlyThe other
UpdateState
is very expensive (time consuming) - e.g. downloading something from internet and then updates the state accordingly.UpdateState
is called only rarely.
My problem is - messages GetState
are blocked and wait until preceding UpdateState
are served. That's why I tried to use TryScan
to process all GetState
messages, but with no luck.
My example code:
type Msg = GetState of AsyncReplyChannel<int> | UpdateState
let mbox = MailboxProcessor.Start(fun mbox ->
let rec loop state = async {
// this TryScan doesn't work as expected
// it should process GetState messages and then continue
mbox.TryScan(fun m ->
match m with
| GetState(chnl) ->
printfn "G processing TryScan"
chnl.Reply(state)
Some(async { return! loop state})
| _ -> None
) |> ignore
let! msg = mbox.Receive()
match msg with
| UpdateState ->
printfn "U processing"
// something very time consuming here...
async { do! Async.Sleep(1000) } |> Async.RunSynchronously
return! loop (state+1)
| GetState(chnl) ->
printfn "G processing"
chnl.Reply(state)
return! loop state
}
loop 0
)
[async { for i in 1..10 do
printfn " U"
mbox.Post(UpdateState)
async { do! Async.Sleep(200) } |> Async.RunSynchronously
};
async { // wait some time so that several `UpdateState` messages are fired
async { do! Async.Sleep(500) } |> Async.RunSynchronously
for i in 1..20 do
printfn "G"
printfn "%d" (mbox.PostAndReply(GetState))
}] |> Async.Parallel |> Async.RunSynchronously
If you try to run the code, you will see, that GetState
message is not almost processed, because it waits for the result. On the other hand UpdateState
is only fire-and-forget, thus blocking effectively getting state.
Edit
Current solution that works for me is this one:
type Msg = GetState of AsyncReplyChannel<int> | UpdateState
let mbox = MailboxProcessor.Start(fun mbox ->
let rec loop state = async {
// this TryScan doesn't work as expected
// it should process GetState messages and then continue
let! res = mbox.TryScan((function
| GetState(chnl) -> Some(async {
chnl.Reply(state)
return state
})
| _ -> None
), 5)
match res with
| None ->
let! msg = mbox.Receive()
match msg with
| UpdateState ->
async { do! Async.Sleep(1000) } |> Async.RunSynchronously
return! loop (state+1)
| _ -> return! loop state
| Some n -> return! loop n
}
loop 0
)
Reactions to comments: the idea with other MailboxProcessor
or ThreadPool
that executes UpdateState
in parallel is great, but I don't need it currently.
All I wanted to do is to process all GetState
messages and after that the others. I don't care that during processing UpdateState
the agent is blocked.
I'll show you what was the problem on the output:
// GetState messages are delayed 500 ms - see do! Async.Sleep(500)
// each UpdateState is sent after 200ms
// each GetState is sent immediatelly! (not real example, but illustrates the problem)
U 200ms <-- issue UpdateState
U processing <-- process UpdateState, it takes 1sec, so other
U 200ms 5 requests are sent; sent means, that it is
U 200ms fire-and-forget message - it doesn't wait for any result
and therefore it can send every 200ms one UpdateState message
G <-- first GetState sent, but waiting for reply - so all
previous UpdateState messages have to be processed! = 3 seconds
and AFTER all the UpdateState messages are processed, result
is returned and new GetState can be sent.
U 200ms
U 200ms because each UpdateState takes 1 second
U 200ms
U processing
U
U
U
U
U processing
G processing <-- now first GetState is processed! so late? uh..
U processing <-- takes 1sec
3
G
U processing <-- takes 1sec
U processing <-- takes 1sec
U processing <-- takes 1sec
U processing <-- takes 1sec
U processing <-- takes 1sec
U processing <-- takes 1sec
G processing <-- after MANY seconds, second GetState is processed!
10
G
G processing
// from this line, only GetState are issued and processed, because
// there is no UpdateState message in the queue, neither it is sent
|> ignore
after theTryScan
call should alert you to the fact that you're using the API wrong. (Don't have time for a complete answer now, hope someone will beat me to it.) – RobillardTryScan
andScan
is to wait for a message and timeout if it's not received. The only difference between the two is that on timeout TryScan returns an option while Scan raises an exception. – Neckwear