2012-12-04 17 views
7

ho creato il seguente semplice HttpListener per servire più richieste contemporaneamente (su .NET 4.5):semplice HtppListener Asynchronous Task-ritorno con async/attendono di spedizione alto carico

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

io uso Apache Benchmark Strumento per caricare questo test. Quando faccio una richiesta 1, ottengo il tempo di attesa massimo per una richiesta di 1 secondo. Se faccio 10 richieste, ad esempio, il tempo di attesa massimo per una risposta arriva a 2 secondi.

Come cambieresti il ​​mio codice precedente per renderlo il più efficiente possibile?

Modifica

Dopo @ di JonSkeet risposta, ho cambiato il codice, come di seguito. Inizialmente, ho provato a simulare una chiamata di blocco, ma credo che fosse il problema principale. Quindi, ho preso il suggerimento di @ JonSkeet e lo cambio in Task.Delay (1000). Ora, il codice sottostante dà max. tempo di attesa come ca. 1 sec per 10 richieste simultanee:

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

Grazie per aver fornito una soluzione completa. –

+0

@JonSkeet quale modifica sarebbe richiesta al modello sopra per consentirgli di servire ** Eventi inviati dal server **, nel qual caso il flusso di risposta non si "chiude" realmente. Ad esempio, come useremmo questo per inviare dati "** continuous push **" ai client connessi? –

+1

@CharlesO Non l'ho provato ma presumo che se non chiami 'output.Close();' sul flusso di output e Flush per ogni push, dovrebbe funzionare (ovviamente, con le intestazioni giuste in atto per SSE). Questo può aiutare anche: http://channel9.msdn.com/Events/TechDays/Techdays-2012-the-Public/2287 – tugberk

risposta

7

Sembra a me come ci si ritroverà con una biforcazione di ascoltatori. Entro ProcessAsync, si avvia una nuova attività da ascoltare (tramite Task.Factory.StartNew), quindi si chiama ProcessAsyncdi nuovo alla fine del metodo. Come può mai finire? Non è chiaro se questa sia la causa dei tuoi problemi di prestazioni, ma sembra decisamente un problema in generale.

Io suggerirei di cambiare il codice per essere solo un semplice ciclo:

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

Ora attualmente il codice qui sopra ignora il valore di ritorno di HandleRequestAsync. È possibile desiderare di mantenere un elenco delle attività "attualmente in corso" e quando è stato chiesto di spegnersi, utilizzare await Task.WhenAll(inFlightTasks) per evitare di arrestare il server troppo rapidamente.

Si noti inoltre che Thread.Sleep è un blocco ritardo. Un ritardo asincrono sarebbe await Task.Delay(1000).

+0

Grazie Jon! Ho messo il ritardo di blocco lì apposta per simulare un'operazione di blocco per vedere come gestirà quelli. – tugberk

+1

@tugberk: Ma l'operazione di blocco potrebbe * a volte * finire per bloccare il thread di accettazione (credo) a causa del modo in cui il tuo codice è scritto al momento. Se stai provando a modellare un pesante lavoro della CPU, dovresti fare in modo che la CPU lavori in un'attività separata e attendere l'attività. Se stai provando a modellare IO, dovrebbe essere atteso in modo asincrono. –

+0

Hai ragione. Il problema principale era (credo) il blocco del "sonno" che ho fatto. Ho modificato la mia domanda e ho inserito il nuovo codice. Manca ancora alcuni passi importanti (immagino) ma ora meglio grazie a te. – tugberk

Problemi correlati