2012-12-04 48 views
7

高い負荷を扱う私は(.NET 4.5に)同時に複数の要求を提供するために、以下の簡単なHttpListenerを作成しました:シンプルなタスクを返す非同期HtppListener非同期/で待つと

class Program { 

    static void Main(string[] args) { 

     HttpListener listener = new HttpListener(); 
     listener.Prefixes.Add("http://+:8088/"); 
     listener.Start(); 
     ProcessAsync(listener).ContinueWith(task => { }); 
     Console.ReadLine(); 
    } 

    static async Task ProcessAsync(HttpListener listener) { 

     HttpListenerContext ctx = await listener.GetContextAsync(); 

     // spin up another listener 
     Task.Factory.StartNew(() => ProcessAsync(listener)); 

     // Simulate long running operation 
     Thread.Sleep(1000); 

     // Perform 
     Perform(ctx); 

     await ProcessAsync(listener); 
    } 

    static void Perform(HttpListenerContext ctx) { 

     HttpListenerResponse response = ctx.Response; 
     string responseString = "<HTML><BODY> Hello world!</BODY></HTML>"; 
     byte[] buffer = Encoding.UTF8.GetBytes(responseString); 

     // Get a response stream and write the response to it. 
     response.ContentLength64 = buffer.Length; 
     Stream output = response.OutputStream; 
     output.Write(buffer, 0, buffer.Length); 

     // You must close the output stream. 
     output.Close(); 
    } 
} 

私はApacheのベンチマークを使用これをテストする負荷ツール。 1回のリクエストをすると、リクエストの最大待ち時間が1秒になります。たとえば、10回のリクエストを行うと、応答の最大待ち時間は2秒になります。

上記のコードを効率的にするにはどうすればよいでしょうか?

編集

@ JonSkeetの答えの後、私は以下のようにコードを変更しました。最初は、ブロッキングコールをシミュレートしようとしましたが、それがコアの問題だったと思います。だから、@ JonSkeetの提案をとり、それをTask.Delay(1000)に変更しました。さて、以下のコードはmaxを与える。おおよその待ち時間。 10個の同時要求のための1秒:あなたはリスナーの分岐点になってしまいますよう

class Program { 

    static bool KeepGoing = true; 
    static List<Task> OngoingTasks = new List<Task>(); 

    static void Main(string[] args) { 

     HttpListener listener = new HttpListener(); 
     listener.Prefixes.Add("http://+:8088/"); 
     listener.Start(); 
     ProcessAsync(listener).ContinueWith(async task => { 

      await Task.WhenAll(OngoingTasks.ToArray()); 
     }); 

     var cmd = Console.ReadLine(); 

     if (cmd.Equals("q", StringComparison.OrdinalIgnoreCase)) { 
      KeepGoing = false; 
     } 

     Console.ReadLine(); 
    } 

    static async Task ProcessAsync(HttpListener listener) { 

     while (KeepGoing) { 
      HttpListenerContext context = await listener.GetContextAsync(); 
      HandleRequestAsync(context); 

      // TODO: figure out the best way add ongoing tasks to OngoingTasks. 
     } 
    } 

    static async Task HandleRequestAsync(HttpListenerContext context) { 

     // Do processing here, possibly affecting KeepGoing to make the 
     // server shut down. 

     await Task.Delay(1000); 
     Perform(context); 
    } 

    static void Perform(HttpListenerContext ctx) { 

     HttpListenerResponse response = ctx.Response; 
     string responseString = "<HTML><BODY> Hello world!</BODY></HTML>"; 
     byte[] buffer = Encoding.UTF8.GetBytes(responseString); 

     // Get a response stream and write the response to it. 
     response.ContentLength64 = buffer.Length; 
     Stream output = response.OutputStream; 
     output.Write(buffer, 0, buffer.Length); 

     // You must close the output stream. 
     output.Close(); 
    } 
} 
+1

完全なソリューションを提供いただき、ありがとうございます。 –

+0

@JonSkeet上記のパターンに**サーバー送信イベント**を提供できるようにするためには、どのような変更が必要でしょうか?その場合、応答ストリームは決して実際には "閉じ"ません。たとえば、これを使用して、接続されたクライアントにデータを「**継続的にプッシュ**」するにはどうすればよいですか? –

+1

@CharlesO私は試していませんが、出力ストリームで 'output.Close();'を呼び出さず、各プッシュでFlushを呼び出すと、正常に動作するはずです(もちろん、SSE )。これも同様に役立ちます:http://channel9.msdn.com/Events/TechDays/Techdays-2012-the-Netherlands/2287 – tugberk

答えて

7

それは私には見えます。 ProcessAsyncの範囲内で、Task.Factory.StartNewを使用して新しいタスクを開始し、メソッドの最後にProcessAsyncにもう一度を呼び出します。どのようにこれまでに完了することができますか?それがパフォーマンスの問題の原因かどうかは明らかではありませんが、間違いなく一般的な問題のように見えます。

私は単純なループであるためにあなたのコードを変更することをお勧めしたい:

static async Task ProcessAsync(HttpListener listener) { 
    while (KeepGoing) { 
     var context = await listener.GetContextAsync(); 
     HandleRequestAsync(context);   
    } 
} 

static async Task HandleRequestAsync(HttpListenerContext context) { 
    // Do processing here, possibly affecting KeepGoing to make the 
    // server shut down. 
} 

は今現在、上記のコードは、HandleRequestAsyncの戻り値を無視します。 "現在の飛行中"のタスクのリストを保持したい場合は、シャットダウンするように求められたら、await Task.WhenAll(inFlightTasks)を使用してサーバーをあまりにも早くダウンさせないようにします。

また、Thread.Sleepは、ブロックの遅延であることにも注意してください。非同期遅延はawait Task.Delay(1000)です。

+0

ありがとうジョン!私はブロッキングディレイをそこに置いて、ブロッキング操作をシミュレートして、それをどのように処理するかを見ています。 – tugberk

+1

@tugberk:しかし、そのブロック操作は*時にはコードが現時点で書かれている方法のために受け入れられているスレッドをブロックしてしまうことがあります。重いCPUの仕事をモデル化しようとしているなら、別のタスクでそのCPUの仕事をして、その仕事を待つべきです。 IOをモデル化しようとするならば、それは非同期的に待たれるべきです。 –

+0

あなたは正しいです。中核となる問題は、私がやった「睡眠」を阻止することでした(私が推測する)。私は自分の質問を編集し、そこに新しいコードを入れました。それはまだいくつかの重要なステップ(私は推測している)を逃しているが、今あなたのおかげでより良い。 – tugberk

関連する問題