简体   繁体   English

F#,MailboxProcessor和Async运行缓慢?

[英]F#, MailboxProcessor and Async running slow?

Background. 背景。

I am trying to figure out MailboxProcessor. 我试图找出MailboxProcessor。 The idea is to use it as a some kind of state machine and pass arguments around between the states and then quit. 想法是将其用作某种状态机,并在状态之间传递参数,然后退出。 Some parts are going to have async communication so I made a Sleep there. 有些部分将进行异步通信,所以我在那里睡觉了。 It's a console application, making a Post does nothing because main thread quits and kills everything behind it. 这是一个控制台应用程序,发布Post不会执行任何操作,因为主线程会退出并杀死其背后的所有内容。 I am making a PostAndReply in main. 我主要是在做一个PostAndReply。 Also, I have tried without 另外,我尝试了

let sleepWorkflow  = async

, doesn't make any difference. ,没有任何区别。

Questions. 问题。

(I am probably doing something wrong) (我可能做错了)

  1. Go24 is not async. Go24不是异步的。 Changing RunSynchronously to StartImmediate makes no visible difference. 将RunSynchronously更改为StartImmediate不会产生明显的区别。 The end should be somewhere below GetMe instead. 末尾应位于GetMe下方。 At the same time Done is printed after Fetch. 同时在提取后打印完成。 Isn't the control supposed t be returned to the main thread on sleep? 难道不应该在睡眠时将控件返回到主线程吗?

    Go24, wait go24 1, end Fetch 1 Done GetMe ... Go24,等待go24 1,结束获取1完成GetMe ...

  2. Run time is terrible slow. 运行时间太慢了。 Without delay in Fetch it's about 10s (stopwatch). 毫不延迟地,获取时间约为10秒(秒表)。 I thought F# threads are lightweight and should use threadpool. 我认为F#线程是轻量级的,应该使用线程池。 According to debugger it takes appr 1s to create every and it looks like real threads. 根据调试器,创建每个应用程序都需要大约1秒钟的时间,并且看起来像真实线程。

Also, changing to [1..100] will "pause" the program for 100s, according to ProcessExplorer 100 threads are created during that time and only then everything is printed. 同样,更改为[1..100]将“暂停”程序100秒钟,根据ProcessExplorer在此期间创建的100个线程,然后才打印所有内容。 I would actually prefer fewer threads and slow increase. 实际上,我更希望线程数量少而增长缓慢。

Code. 码。

Program.fs Program.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

Mailbox.fs Mailbox.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 )

I'm not necessarily sure that this is the main problem, but the nested asyncs and Async.RunSynchrously in the agent code look suspicious. 我不一定要确定这是主要问题,但是代理代码中嵌套的asyncs和Async.RunSynchrously看起来很可疑。

You do not need to create a nested async - you can just call asynchronous operations in the body of the match clauses directly: 您无需创建嵌套的异步-您可以直接在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) ->
      (...)

Aside from that, it is important to understand that the agent has exactly one instance of the body computation running. 除此之外,重要的是要了解该代理恰好具有运行主体计算的一个实例。 So, if you block the body of the agent, all other operations will be queued. 因此,如果您阻止该代理的主体,则所有其他操作都将排队。

If you want to start some task (like the synchronous operations) in the background and resume the agent immediately, you can use Async.Start inside the body (but be sure to call the main loop recursively in the main part of the body): 如果要在后台启动某些任务(例如同步操作)并立即恢复代理,则可以在主体内部使用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()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM