2012-12-04 11 views
7

stworzyłem następujący prosty HttpListener służyć wiele żądań w tym samym czasie (na .NET 4.5):proste zadanie, wracając z Asynchronous HtppListener asynchroniczny/czekają i obsługi dużego obciążenia

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(); 
    } 
} 

używam Apache Benchmark Narzędzie do testu obciążenia. Kiedy wykonam 1 żądanie, otrzymam maksymalny czas oczekiwania na żądanie jako 1 sekundę. Jeśli wykonam 10 żądań, na przykład maksymalny czas oczekiwania na odpowiedź wzrośnie do 2 sekund.

Jak zmienić powyższy kod, aby był tak wydajny, jak to tylko możliwe?

Edit

Po użytkownika @ JonSkeet odpowiedź, Zmieniłem kod jak poniżej. Początkowo próbowałem symulować wywołanie blokujące, ale myślę, że to był podstawowy problem. Tak więc podjąłem sugestię @ JonSkeeta i zmienię ją na Task.Delay (1000). Teraz poniższy kod podaje max. czas oczekiwania ok. 1 s dla 10 jednoczesnych żądań:

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

Dzięki za dostarczenie kompletnego rozwiązania. –

+0

@JonZmień, jaka modyfikacja będzie wymagana dla powyższego wzorca, aby umożliwić jej wyświetlanie ** Zdarzenia wysłane przez serwer **, w którym to przypadku strumień odpowiedzi nigdy naprawdę nie "zamyka się". Na przykład, w jaki sposób wykorzystamy to do "** ciągłego przekazywania **" danych do podłączonych klientów? –

+1

@CharlesO Nie próbowałem go, ale zakładam, że jeśli nie wywołasz 'output.Close();' w strumieniu wyjściowym i Flush dla każdego push, to powinno działać (oczywiście, z właściwymi nagłówkami dla SSE). Może to również pomóc: http://channel9.msdn.com/Events/TechDays/Techdays-2012-the-Netherlands/2287 – tugberk

Odpowiedz

7

Wygląda na to, że skończysz z bifurkacją słuchaczy. W ramach ProcessAsync, rozpoczynasz nowe zadanie do nasłuchu (przez Task.Factory.StartNew), a następnie na końcu metody ponownie wywołujesz ProcessAsync. Jak to się może kiedykolwiek skończyć? Nie jest jasne, czy to jest przyczyną problemów z wydajnością, ale zdecydowanie wygląda na problem w ogóle.

Sugeruję zmianę kodu być tylko prosta pętla:

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. 
} 

Teraz aktualnie powyższy kod ignoruje wartość zwracaną HandleRequestAsync. Ty może chcesz zachować listę zadań "obecnie w locie", a gdy zostaniesz poproszony o zamknięcie, użyj await Task.WhenAll(inFlightTasks), aby uniknąć zbyt szybkiego zaniku serwera.

Należy również pamiętać, że Thread.Sleep jest opóźnieniem w postaci blokowania. Opóźnienie asynchroniczne to await Task.Delay(1000).

+0

Dzięki Jon! Opróżniłem tam opóźnienie blokowania, aby zasymulować operację blokowania, aby zobaczyć, jak sobie z tym poradzi. – tugberk

+1

@tugberk: Ale ta operacja blokowania może * czasami * zakończyć blokowanie wątku akceptującego (tak myślę) ze względu na sposób, w jaki twój kod został napisany w tej chwili. Jeśli próbujesz modelować ciężką pracę procesora, powinieneś wykonać tę pracę CPU w oddzielnym zadaniu i poczekać na zadanie. Jeśli próbujesz modelować IO, to powinno być oczekiwane asynchronicznie. –

+0

Masz rację. Głównym problemem był (jak sądzę) blokujący "sen", który zrobiłem. Poprawiłem moje pytanie i umieściłem tam nowy kod. Ciągle pomija kilka ważnych kroków (zgaduję), ale teraz lepiej dzięki tobie. – tugberk

Powiązane problemy