Concurrency 如何正确使用F#中的TryScan

Concurrency 如何正确使用F#中的TryScan,concurrency,f#,messages,mailboxprocessor,Concurrency,F#,Messages,Mailboxprocessor,我想找一个关于如何使用TryScan的例子,但是没有找到,你能帮我吗 我想做的(相当简单的示例):我有一个接受 两种类型的台面 第一个GetState返回当前状态。 GetState消息发送非常频繁 另一个UpdateState非常昂贵(耗时)-例如,从internet下载某些内容,然后相应地更新状态。 UpdateState很少被调用 我的问题是-消息GetState被阻止,并等待前面的UpdateState被送达。这就是为什么我尝试使用TryScan来处理所有GetState消息,但没有

我想找一个关于如何使用TryScan的例子,但是没有找到,你能帮我吗

我想做的(相当简单的示例):我有一个接受 两种类型的台面

  • 第一个
    GetState
    返回当前状态。
    GetState
    消息发送非常频繁

  • 另一个
    UpdateState
    非常昂贵(耗时)-例如,从internet下载某些内容,然后相应地更新状态。
    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