3

長い投稿で申し訳ありません。TcpListenerポートでリッスンし、別の (バックグラウンド) スレッドで着信接続によって要求された重い作業を処理し、準備ができたらクライアントに応答を返すために使用したいと思います。私は MSDN で多くのコードとサンプルを読み、サーバーの次の実装を考え出しました。

以下のすべての実装について、次の変数を想定してください。

let sva = "127.0.0.1"
let dspt = 32000

let respondToQuery (ns_ : NetworkStream) (bta_ : byte array) : unit =
    // DO HEAVY LIFTING
    ()

実装 1 (単純な同期サーバー。この MSDN ページのコードを翻訳したもの)

let runSync () : unit =
    printfn "Entering runSync ()"
    let (laddr : IPAddress) = IPAddress.Parse sva
    let (svr : TcpListener) = new TcpListener (laddr, dspt)
    try
        svr.Start ()
        let (bta : byte array) = Array.zeroCreate<byte> imbs
        while true do
            printfn "Listening on port %d at %s" dspt sva
            let (cl : TcpClient) = svr.AcceptTcpClient ()
            let (ns : NetworkStream) = cl.GetStream ()
            respondToQuery ns bta
            cl.Close ()
        svr.Stop ()
        printfn "Exiting runSync () normally"
    with
        | excp ->
            printfn "Error:  %s" excp.Message
            printfn "Exiting runSync () with error"

実装 2 (この MSDN ページのコードの私の翻訳)

let runAsyncBE () : unit =
    printfn "Entering runAsyncBE ()"
    let (tcc : ManualResetEvent) = new ManualResetEvent (false)
    let (bta : byte array) = Array.zeroCreate<byte> imbs
    let datcc (ar2_ : IAsyncResult) : unit =
        let tcpl2 = ar2_.AsyncState :?> TcpListener
        let tcpc2 = tcpl2.EndAcceptTcpClient ar2_
        let (ns2 : NetworkStream) = tcpc2.GetStream ()
        respondToQuery ns2 bta
        tcpc2.Close ()
        tcc.Set () |> ignore
    let rec dbatc (tcpl2_ : TcpListener) : unit =
        tcc.Reset () |> ignore
        printfn "Listening on port %d at %s" dspt sva
        tcpl2_.BeginAcceptTcpClient (new AsyncCallback (datcc), tcpl2_) |> ignore
        tcc.WaitOne () |> ignore
        dbatc tcpl2_
    let (laddr : IPAddress) = IPAddress.Parse sva
    let (tcpl : TcpListener) = new TcpListener (laddr, dspt)
    try
        tcpl.Start ()
        dbatc tcpl
        printfn "Exiting try block"
        printfn "Exiting runAsyncBE () normally"
    with
        | excp ->
            printfn "Error:  %s" excp.Message
            printfn "Exiting runAsyncBE () with error"

実装 3 (非同期ワークフローの MSDN ページに基づく私の実装)

let runAsyncA () : unit =
    printfn "Entering runAsyncA ()"
    let (laddr : IPAddress) = IPAddress.Parse sva
    let (svr : TcpListener) = new TcpListener (laddr, dspt)
    try
        svr.Start ()
        let (bta : byte array) = Array.zeroCreate<byte> imbs
        while true do
            printfn "Listening on port %d at %s" dspt sva
            let (cl : TcpClient) = svr.AcceptTcpClient ()
            let (ns : NetworkStream) = cl.GetStream ()
            async {respondToQuery ns bta} |> Async.RunSynchronously
            cl.Close ()
        svr.Stop ()
        printfn "Exiting runAsyncA () normally"
    with
        | excp ->
            printfn "Error:  %s" excp.Message
            printfn "Exiting runAsyncA () with error"

Implementation 3さて、MSDN のドキュメントを読んだところ、それが最速だったと思います。しかし、複数のマシンからの複数のクエリでサーバーにヒットすると、それらはすべてほぼ同じ速度で動作します。これにより、私は何か間違ったことをしているに違いないと信じるようになります。

バックグラウンドで重労働を行い、終了時にクライアントに応答を返し、別のクライアントが別のバックグラウンドスレッドで別のタスクに接続して開始できるようにする実装の「正しい」方法はどちらImplementation 2ですか? そうでない場合は、どのクラス (またはチュートリアル) を読むべきか教えてください。Implementation 3TcpListener

4

1 に答える 1

6

メイン ループの正しい構造は次のようになります。

let respondToQuery (client:TcpClient) = async {
  try
    let stream = client.GetStream()
    () // TODO: The actual processing goes here!
  finally
    client.Close() }

async {
  while true do 
    let! client = t.AcceptTcpClientAsync() |> Async.AwaitTask
    respondToQuery client |> Async.Start }

注意すべき重要事項は次のとおりです。

  • メインループを内側にラップしてasync、クライアントを非同期的に使用して待機できるようにしましたAcceptTcpClientAsync(そこでブロックすることなく)

  • このrespondToQuery関数は、 を使用してバックグラウンドで開始される非同期計算を返しますAsync.Start。これにより、次のクライアントを待機しながら処理を並行して続行できます (使用Async.RunSynchronouslyする場合は、ブロックして完了するまで待機しますrespondToQuery) 。

  • これを完全に非同期にするには、内部のコードrespondToQueryでもストリームの非同期操作を使用する必要がAsyncReadありAsyncWriteます。

を使用することもできますAsync.StartChild。この場合、子計算 ( の本体respondToQuery) は親と同じキャンセル トークンを取得するため、メインの非同期ワークフローをキャンセルすると、すべての子もキャンセルされます。

  while true do 
    let! client = t.AcceptTcpClientAsync() |> Async.AwaitTask
    do! respondToQuery client |> Async.StartChild |> Async.Ignore }

このAsync.StartChildメソッドは ( let!orを使用して開始されるdo!) 非同期計算を返し、それが返すトークンを無視する必要があります (子が完了するまで待機するために使用できます)。

于 2013-03-07T14:03:50.533 に答える