【问题标题】:F#, MailboxProcessor and Async running slow?F#、MailboxProcessor 和 Async 运行缓慢?
【发布时间】:2015-04-01 15:47:44
【问题描述】:

背景。

我正在尝试找出 MailboxProcessor。这个想法是将它用作某种状态机并在状态之间传递参数然后退出。有些部分将进行异步通信,所以我在那里做了一个睡眠。 它是一个控制台应用程序,因为主线程退出并杀死它背后的一切,所以创建 Post 什么都不做。我正在主要制作 PostAndReply。 另外,我试过没有

let sleepWorkflow  = async

,没有任何区别。

问题。

(我可能做错了什么)

  1. Go24 不是异步的。将 RunSynchronously 更改为 StartImmediate 没有明显的区别。结尾应该在 GetMe 下面的某个地方。同时在 Fetch 之后打印出 Done。控制不是应该在休眠时返回给主线程吗?

    Go24,等等 go24 1,结束 获取 1 完毕 获取我 ...

  2. 运行时间非常慢。毫不拖延地获取大约 10 秒(秒表)。我认为 F# 线程是轻量级的,应该使用线程池。 根据调试器,创建每个线程需要大约 1 秒,它看起来像真正的线程。

此外,更改为 [1..100] 将“暂停”程序 100 秒,根据 ProcessExplorer,在此期间创建了 100 个线程,然后才打印所有内容。我实际上更喜欢更少的线程和缓慢的增长。

代码。

程序.fs

[<EntryPoint>]
let main argv =


    let a = Mailbox.MessageBasedCounter.DoGo24 1
    let a = Mailbox.MessageBasedCounter.DoFetch 1
    let b = Mailbox.MessageBasedCounter.GetMe

    let task i  = async {
        //Mailbox.MessageBasedCounter.DoGo24 1
        let a = Mailbox.MessageBasedCounter.DoFetch i
        return a
        }

    let stopWatch = System.Diagnostics.Stopwatch.StartNew()

    let x = 
        [1..10]
            |> Seq.map task
            |> Async.Parallel
            |> Async.RunSynchronously

    stopWatch.Stop()
    printfn "%f" stopWatch.Elapsed.TotalMilliseconds

    printfn "a: %A" a
    printfn "b: %A" b

    printfn "x: %A" x
    0 // return an integer exit code

邮箱.fs

module Mailbox

#nowarn "40"

type parserMsg =
    | Go24 of int
    | Done
    | Fetch of int * AsyncReplyChannel<string>
    | GetMe of AsyncReplyChannel<string>


type MessageBasedCounter () = 

    /// Create the agent
    static let agent = MailboxProcessor.Start(fun inbox -> 

        // the message processing function
        let rec messageLoop() = async{
            let! msg = inbox.Receive()

            match msg with 
            | Go24 n ->
                let sleepWorkflow  = async{
                    printfn "Go24, wait"
                    do! Async.Sleep 4000 
                    MessageBasedCounter.DoDone() // POST Done.
                    printfn "go24 %d, end" n
                    return! messageLoop()
                } 
                Async.RunSynchronously sleepWorkflow
            | Fetch (i, repl) ->
                let sync = async{
                    printfn "Fetch %d" i
                    do! Async.Sleep 1000
                    repl.Reply( "Reply Fetch " + i.ToString() ) // Reply to the caller 
                    return! messageLoop()
                }
                Async.RunSynchronously sync

            | GetMe (repl) ->
                let sync = async{
                    printfn "GetMe"
                    repl.Reply( "GetMe" ) // Reply to the caller 
                    return! messageLoop()
                }
                Async.RunSynchronously sync
            | Done -> 
                let sync = async{
                    printfn "Done"
                    return! messageLoop()
                }
                Async.RunSynchronously sync 
            }

        // start the loop 
        messageLoop()
        )

    // public interface to hide the implementation
    static member DoDone () = agent.Post( Done )
    static member DoGo24 (i:int) = agent.Post( Go24(i) )
    static member DoFetch (i:int) = agent.PostAndReply( fun reply -> Fetch(i, reply) )
    static member GetMe = agent.PostAndReply( GetMe )

【问题讨论】:

    标签: f# mailboxprocessor


    【解决方案1】:

    我不一定确定这是主要问题,但代理代码中的嵌套异步和Async.RunSynchrously 看起来很可疑。

    您不需要创建嵌套异步 - 您可以直接在 match 子句的主体中调用异步操作:

    // the message processing function
    let rec messageLoop() = async{
      let! msg = inbox.Receive()
    
      match msg with 
      | Go24 n ->
          printfn "Go24, wait"
          do! Async.Sleep 4000 
          MessageBasedCounter.DoDone()
          printfn "go24 %d, end" n
          return! messageLoop()
    
      | Fetch (i, repl) ->
          (...)
    

    除此之外,重要的是要了解代理只有一个正在运行的主体计算实例。所以,如果你阻塞了代理的主体,所有其他的操作都会排队。

    如果你想在后台启动一些任务(比如同步操作)并立即恢复代理,你可以在主体内部使用Async.Start(但一定要在主体部分递归调用主循环)正文):

      | Go24 n ->
          // Create work item that will run in the background
          let work = async {
            printfn "Go24, wait"
            do! Async.Sleep 4000 
            MessageBasedCounter.DoDone()
            printfn "go24 %d, end" n }
          // Queue the work in a thread pool to be processed
          Async.Start(work)
          // Continue the message loop, waiting for other messages
          return! messageLoop()
    

    【讨论】:

    • 啊哈,谢谢。我正试图像你说的那样恢复代理。看起来我的错误是调用return!异步中的 messageLoop()。这不会恢复代理。我使用外部异步作为解决方法,因为它需要 Seq.map 作为 Parallell。运行 let x = [1..10] |> Seq.map Mailbox.MessageBasedCounter.DoFetch 在达到 10 之前中断程序。 List.map 运行到 10 没有问题。只是一个有趣的时刻。
    猜你喜欢
    • 2018-10-12
    • 2014-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-25
    • 2020-07-22
    • 1970-01-01
    相关资源
    最近更新 更多