1

バックグラウンド。

MailboxProcessor を把握しようとしています。アイデアは、ある種のステート マシンとして使用し、ステート間で引数を渡し、終了することです。一部の部分は非同期通信を行う予定なので、そこで Sleep を作成しました。これはコンソール アプリケーションです。メイン スレッドが終了し、その背後にあるすべてのものを強制終了するため、Post を作成しても何もしません。メインで PostAndReply を作成しています。また、私はなしで試しました

let sleepWorkflow  = async

、違いはありません。

質問。

(多分私は何か間違ったことをしている)

  1. Go24 は非同期ではありません。RunSynchronously を StartImmediate に変更しても、目に見える違いはありません。代わりに、末尾は GetMe の下のどこかにあるはずです。同時に、Fetch の後に Done が出力されます。スリープ時に制御がメインスレッドに戻されるはずがありませんか?

    Go24、待って go24 1、フェッチを終了 1 完了 GetMe ...

  2. 実行時間はひどく遅いです。Fetch の遅延なしで、約 10 秒 (ストップウォッチ) です。F# スレッドは軽量であり、threadpool を使用する必要があると考えました。デバッガーによると、すべてを作成するのに appr 1 秒かかり、実際のスレッドのように見えます。

また、[1..100] に変更すると、100 秒間プログラムが「一時停止」されます。ProcessExplorer によると、その間に 100 のスレッドが作成され、その後すべてが出力されます。私は実際には、より少ないスレッドとゆっくりとした増加を好みます。

コード。

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

メールボックス.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 )
4

1 に答える 1

2

これが主な問題であるとは必ずしも確信していませんが、ネストされた非同期と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) ->
      (...)

それとは別に、エージェントが実行中のボディ計算のインスタンスを 1 つだけ持っていることを理解することが重要です。したがって、エージェントの本体をブロックすると、他のすべての操作がキューに入れられます。

バックグラウンドでいくつかのタスク (同期操作など) を開始し、すぐにエージェントを再開したい場合は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()
于 2015-04-01T16:03:19.910 に答える