Concurrency 如何正确使用F#中的TryScan
我想找一个关于如何使用TryScan的例子,但是没有找到,你能帮我吗 我想做的(相当简单的示例):我有一个接受 两种类型的台面Concurrency 如何正确使用F#中的TryScan,concurrency,f#,messages,mailboxprocessor,Concurrency,F#,Messages,Mailboxprocessor,我想找一个关于如何使用TryScan的例子,但是没有找到,你能帮我吗 我想做的(相当简单的示例):我有一个接受 两种类型的台面 第一个GetState返回当前状态。 GetState消息发送非常频繁 另一个UpdateState非常昂贵(耗时)-例如,从internet下载某些内容,然后相应地更新状态。 UpdateState很少被调用 我的问题是-消息GetState被阻止,并等待前面的UpdateState被送达。这就是为什么我尝试使用TryScan来处理所有GetState消息,但没有
- 第一个
返回当前状态。GetState
消息发送非常频繁GetState
- 另一个
非常昂贵(耗时)-例如,从internet下载某些内容,然后相应地更新状态。UpdateState
很少被调用UpdateState
GetState
被阻止,并等待前面的UpdateState
被送达。这就是为什么我尝试使用TryScan
来处理所有GetState
消息,但没有成功
我的示例代码:
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
对评论的反应:使用其他
MailboxProcessor
或ThreadPool
并行执行UpdateState
的想法非常好,但我目前不需要它。
我想做的就是处理所有GetState
消息,然后处理其他消息。我不在乎在处理UpdateState
过程中代理被阻止
我将向您展示输出上的问题:
// 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
//GetState消息延迟500毫秒-请参阅!异步睡眠(500)
//每200毫秒后发送一次更新
//每个GetState都会立即发送!(不是真实的例子,但说明了问题)
U 200ms我认为TryScan
方法在这种情况下对您没有帮助。它允许您指定等待消息时要使用的超时。一旦收到某条消息,它将开始处理该消息(忽略超时)
例如,如果您想等待某个特定消息,但每秒(等待时)执行一些其他检查,则可以编写:
let loop () = async {
let! res = mbox.TryScan(function
| ImportantMessage -> Some(async {
// process message
return 0
})
| _ -> None)
match res with
| None ->
// perform some check & continue waiting
return! loop ()
| Some n ->
// ImportantMessage was received and processed
}
在处理UpdateState
邮件时,如何避免阻塞邮箱处理器?邮箱处理器(逻辑上)是单线程的-您可能不想取消对UpdateState
消息的处理,因此最好的选择是在后台开始处理它,并等待处理完成。然后,处理UpdateState
的代码可以将一些消息发送回邮箱(例如UpdateStateCompleted
)
下面是一个草图,它可能看起来是什么样子:
let rec loop (state) = async {
let! msg = mbox.Receive()
match msg with
| GetState(repl) ->
repl.Reply(state)
return! scanning state
| UpdateState ->
async {
// complex calculation (runs in parallel)
mbox.Post(UpdateStateCompleted newState) }
|> Async.Start
| UpdateStateCompleted newState ->
// Received new state from background workflow
return! loop newState }
既然后台任务是并行运行的,那么需要注意可变状态。此外,如果您发送UpdateState
消息的速度快于您处理它们的速度,您将遇到麻烦。例如,当您已经在处理前一个请求时,可以通过忽略或排队请求来解决此问题。正如Tomas提到的,MailboxProcessor是单线程的。您将需要另一个MailboxProcessor在与state getter不同的线程上运行更新
#nowarn "40"
type Msg =
| GetState of AsyncReplyChannel<int>
| UpdateState
let runner_UpdateState = MailboxProcessor.Start(fun mbox ->
let rec loop = async {
let! state = mbox.Receive()
printfn "U start processing %d" !state
// something very time consuming here...
do! Async.Sleep 100
printfn "U done processing %d" !state
state := !state + 1
do! loop
}
loop
)
let mbox = MailboxProcessor.Start(fun mbox ->
// we need a mutiple state if another thread can change it at any time
let state = ref 0
let rec loop = async {
let! msg = mbox.Receive()
match msg with
| UpdateState -> runner_UpdateState.Post state
| GetState chnl -> chnl.Reply !state
return! loop
}
loop)
[
async {
for i in 1..10 do
mbox.Post UpdateState
do! Async.Sleep 200
};
async {
// wait some time so that several `UpdateState` messages are fired
do! Async.Sleep 1000
for i in 1..20 do
printfn "G %d" (mbox.PostAndReply GetState)
do! Async.Sleep 50
}
]
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
System.Console.ReadLine() |> ignore
您也可以使用线程池
open System.Threading
type Msg =
| GetState of AsyncReplyChannel<int>
| SetState of int
| UpdateState
let mbox = MailboxProcessor.Start(fun mbox ->
let rec loop state = async {
let! msg = mbox.Receive()
match msg with
| UpdateState ->
ThreadPool.QueueUserWorkItem((fun obj ->
let state = obj :?> int
printfn "U start processing %d" state
Async.Sleep 100 |> Async.RunSynchronously
printfn "U done processing %d" state
mbox.Post(SetState(state + 1))
), state)
|> ignore
| GetState chnl ->
chnl.Reply state
| SetState newState ->
return! loop newState
return! loop state
}
loop 0)
[
async {
for i in 1..10 do
mbox.Post UpdateState
do! Async.Sleep 200
};
async {
// wait some time so that several `UpdateState` messages are fired
do! Async.Sleep 1000
for i in 1..20 do
printfn "G %d" (mbox.PostAndReply GetState)
do! Async.Sleep 50
}
]
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
开放系统线程
类型Msg=
|获取AsyncReplyChannel的状态
|int设置状态
|不动产
让mbox=MailboxProcessor.Start(有趣的mbox->
让rec循环状态=异步{
let!msg=mbox.Receive()
配味精
|更新地产->
ThreadPool.QueueUserWorkItem((有趣的对象->
让状态=对象:?>int
printfn“U开始处理%d”状态
Async.Sleep 100 |>Async.RunSynchronously
printfn“U已完成处理%d”状态
mbox.Post(设置状态(状态+1))
),州)
|>忽略
|GetState chnl->
中国答复国
|设置状态新闻状态->
返回!循环新闻状态
返回!循环状态
}
循环0)
[
异步{
因为我在1..10做
mbox.邮政房地产
do!Async.Sleep 200
};
异步{
//等待一段时间,以便触发多条“UpdateEstate”消息
do!Async.Sleep 1000
因为我在1..20做
打印fn“G%d”(mbox.PostAndReply GetState)
做!异步。睡眠50
}
]
|>异步并行
|>异步运行
|>忽略
System.Console.ReadLine()|>忽略不要使用TRYSCAN强>
不幸的是,当前版本的F#中的TryScan
功能在两个方面被破坏。首先,整个要点是指定一个超时,但实现实际上并不遵守它。具体来说,不相关的消息会重置计时器。其次,与另一个Scan
函数一样,消息队列是在一个锁下检查的,该锁可以防止任何其他线程在扫描期间发布消息,该扫描可以是任意长的时间。因此,TryScan
函数本身往往会锁定并发系统,甚至会引入死锁,因为调用方的代码是在锁中计算的(例如,当锁下的代码阻塞等待获取其已处于的锁时,从函数参数过帐到Scan
或TryScan
会使代理死锁)
我在我的产品代码的早期原型中使用了TryScan
,它导致了无数的问题。然而,我设法围绕它构建了一个体系结构,最终的体系结构实际上更好。本质上,我急切地接收所有消息,并使用我自己的本地队列进行过滤。事实上,你有|>忽略r调用TryScan
应该会提醒您API使用错误。(现在没有时间给出完整的答案,希望有人能抢先回答。)我知道我用错了。但是我还没有找到任何关于使用它的帖子。我认为TryScan
和Scan
的要点是等待消息,如果没有收到消息,则超时。两者之间的唯一区别是,on timeout TryScan返回一个选项
U start processing 0
U done processing 0
U start processing 1
U done processing 1
U start processing 2
U done processing 2
U start processing 3
U done processing 3
U start processing 4
U done processing 4
G 5
U start processing 5
G 5
U done processing 5
G 5
G 6
U start processing 6
G 6
G 6
U done processing 6
G 7
U start processing 7
G 7
G 7
U done processing 7
G 8
G U start processing 8
8
G 8
U done processing 8
G 9
G 9
U start processing 9
G 9
U done processing 9
G 9
G 10
G 10
G 10
G 10
open System.Threading
type Msg =
| GetState of AsyncReplyChannel<int>
| SetState of int
| UpdateState
let mbox = MailboxProcessor.Start(fun mbox ->
let rec loop state = async {
let! msg = mbox.Receive()
match msg with
| UpdateState ->
ThreadPool.QueueUserWorkItem((fun obj ->
let state = obj :?> int
printfn "U start processing %d" state
Async.Sleep 100 |> Async.RunSynchronously
printfn "U done processing %d" state
mbox.Post(SetState(state + 1))
), state)
|> ignore
| GetState chnl ->
chnl.Reply state
| SetState newState ->
return! loop newState
return! loop state
}
loop 0)
[
async {
for i in 1..10 do
mbox.Post UpdateState
do! Async.Sleep 200
};
async {
// wait some time so that several `UpdateState` messages are fired
do! Async.Sleep 1000
for i in 1..20 do
printfn "G %d" (mbox.PostAndReply GetState)
do! Async.Sleep 50
}
]
|> Async.Parallel
|> Async.RunSynchronously
|> ignore