问题
In the following code, both do! ag.AsyncAdd (Some i)
or ag.AsyncAdd (Some i)
(in the function enqueue()
) work. What's the difference between them? It seems do! ...
will make enqueuing and dequeuing calls more mixed? How?
open FSharpx.Control
let test () =
let ag = new BlockingQueueAgent<int option>(500)
let enqueue() = async {
for i = 1 to 15 do
// ag.AsyncAdd (Some i) // works too
do! ag.AsyncAdd (Some i)
printfn "=> %d" i }
async {
do! [ for i = 1 to 10 do yield enqueue() ]
|> Async.Parallel |> Async.Ignore
for i = 1 to 5 do ag.Add None
} |> Async.Start
let rec dequeue() =
async {
let! m = ag.AsyncGet()
match m with
| Some v ->
printfn "<= %d" v
return! dequeue()
| None ->
printfn "Done"
}
[ for i = 1 to 5 do yield dequeue() ]
|> Async.Parallel |> Async.Ignore |> Async.RunSynchronously
0
回答1:
From FSharpx source code (see comments):
/// Asynchronously adds item to the queue. The operation ends when
/// there is a place for the item. If the queue is full, the operation
/// will block until some items are removed.
member x.AsyncAdd(v:'T, ?timeout) =
agent.PostAndAsyncReply((fun ch -> AsyncAdd(v, ch)), ?timeout=timeout)
When you do not use do!, you do not block enqueue thread in case if queue is full (500 items in queue as you state in constructor). So, when you changed loops to higher number, you spammed MailboxProcessor queue with messages (behind the scene FSharpx uses MailboxProcessor - check docs for this class) of type AsyncAdd from all iterations of all enqueue thread. This slows down another operation, agent.Scan:
and fullQueue() =
agent.Scan(fun msg ->
match msg with
| AsyncGet(reply) -> Some(dequeueAndContinue(reply))
| _ -> None )
- because you have in queue a lot of AsyncAdd and AsyncGet.
In case, when you put do! before AsyncAdd, you enqueue threads will be blocked at moment when there are 500 items in queue and no additional messages would be generated for MailboxProcessor, thus agent.Scan will work fast. When dequeue thread takes an item and the number of them becomes 499, new enqueue thread awaiks and adds new item and then goes to next iteration of a loop, put new AsyncAdd message into MailboxProcessor and again, goes to sleep till moment of dequeue. Thus, MailboxProcessor is not spammed with messages AsyncAdd of all iterations of one enqueue thread. Note: queue of items and queue of MailboxProcessor messages are different queues.
回答2:
Inside any F# computation expression, any keyword that ends with !
tends to mean "Handle this one specially, according to the rules of this block". E.g., in an async { }
block, the let!
keyword means "await the result, then assign the result to this variable" and the do!
keyword means "await this asynchronous operation, but throw away the result and don't assign it to anything". If you don't use a do!
keyword, then you are not awaiting the result of that operation.
So with a do!
keyword inside your enqueue
function, you are doing the following fifteen times:
- Kick off an
AsyncAdd
operation - Wait for it to complete
- print "=> 1" (or 2, or 3...)
Without a do!
keyword, you are doing the following:
- Kick off fifteen
AsyncAdd
operations as fast as possible - After kicking each one off, print "=> 1" (or 2, or 3...)
It sounds like you don't yet fully understand how F#'s computation expressions work behind the scenes. I recommend reading Scott Wlaschin's excellent site to gain more understanding: first https://fsharpforfunandprofit.com/posts/concurrency-async-and-parallel/ and then https://fsharpforfunandprofit.com/series/computation-expressions.html so that when you read the second series of articles, you're building on a bit of existing knowledge.
来源:https://stackoverflow.com/questions/57651745/whats-different-between-async-asyncadd-and-async-do-asyncadd