13

Sto provando a eseguire il benchmark (utilizzando Apache bench) un paio di endpoint ASP.NET Web 2.0. Uno dei quali è sincrono e uno asincrono.ASP.NET Web API 2 Metodi di azione asincrona con Task.Run prestazioni

 [Route("user/{userId}/feeds")] 
     [HttpGet] 
     public IEnumerable<NewsFeedItem> GetNewsFeedItemsForUser(string userId) 
     { 
      return _newsFeedService.GetNewsFeedItemsForUser(userId); 
     } 

     [Route("user/{userId}/feeds/async")] 
     [HttpGet] 
     public async Task<IEnumerable<NewsFeedItem>> GetNewsFeedItemsForUserAsync(string userId) 
     { 
      return await Task.Run(() => _newsFeedService.GetNewsFeedItemsForUser(userId)); 
     } 

Dopo aver visto Steve Sanderson's presentation ho emesso il seguente comando ab -n 100 -c 10 http://localhost.... per ciascun endpoint.

Sono rimasto sorpreso dal fatto che i parametri di riferimento per ciascun endpoint sembravano approssimativamente uguali.

Andando fuori quello che Steve ha spiegato che mi aspettavo che l'endpoint asincrono sarebbe più performante perché avrebbe liberato le discussioni pool di thread di nuovo al pool di thread immediatamente, rendendoli così disponibili per altre richieste e produttività migliorando. Ma i numeri sembrano esattamente gli stessi.

Cosa sto fraintendendo qui?

risposta

18

Utilizzando await Task.Run per creare "asincrona" WebAPI è una cattiva idea - si continua a utilizzare un filo, e anche da the same thread pool used for requests.

ciò porterà ad alcuni momenti spiacevoli descritte in buone dettagli here:

  • extra (inutile) commutazione filo a filo piscina filo Task.Run. Allo stesso modo, quando quel thread termina la richiesta, deve inserire nel contesto della richiesta (che non è un vero thread switch ma ha overhead).
  • La garbage extra (non necessaria) viene creata. La programmazione asincrona è un compromesso: si ottiene una maggiore reattività a scapito dell'uso più elevato della memoria . In questo caso, si finisce per creare più spazzatura per le operazioni asincrone che è totalmente inutile.
  • L'euristica del pool di thread ASP.NET viene espulsa da Task.Run "inaspettatamente" prendendo in prestito un thread del thread. Non ho un sacco di esperienza qui, ma il mio istinto mi dice che l'euristica dovrebbe recuperare bene se l'attività inaspettata è veramente breve e non la gestirà in modo elegante se l'attività inaspettata dura più di due secondi .
  • ASP.NET non è in grado di terminare anticipatamente la richiesta, ad es. Se il client si disconnette o la richiesta scade. Nel caso sincrono, ASP.NET conosceva il thread di richiesta e poteva interromperlo. Nel caso asincrono , ASP.NET non è a conoscenza del fatto che il thread di thread secondario thread è "per" quella richiesta. È possibile risolvere questo problema utilizzando i token di annullamento , ma questo non rientra nell'ambito di questo post del blog.

Fondamentalmente, non consentono alcuna asincronia al ASP.NET - basta nascondere il codice sincrono CPU-bound dietro la facciata asincrona. Async da solo è ideale per il codice rilegato I/O, perché consente di utilizzare CPU (thread) con la massima efficienza (nessun blocco per I/O), ma se si dispone del codice Compute, sarà comunque necessario CPU nella stessa misura.

E tenendo conto dell'overhead aggiuntivo da Task e commutazione di contesto otterrete risultati ancora peggiori rispetto ai semplici metodi di controllo sync.

COME per renderla veramente ASYNC: metodo

GetNewsFeedItemsForUser sarà mutato in async.

[Route("user/{userId}/feeds/async")] 
    [HttpGet] 
    public async Task<IEnumerable<NewsFeedItem>> GetNewsFeedItemsForUserAsync(string userId) 
    { 
     return await _newsFeedService.GetNewsFeedItemsForUser(userId); 
    } 

per farlo:

  • Se si tratta di un metodo biblioteca quindi cercare la sua async variante (se non ce ne sono - sfortuna, si dovrà cercare qualche analogico in competizione).
  • Se si tratta del metodo personalizzato che utilizza il file system o il database, sfruttare le funzionalità asincrone per creare API asincrone per il metodo.
+1

Ok, ho capito che Task.Run è una cattiva idea in ASP.NET perché usa una discussione dal pool di thread. Suppongo che ora le mie domande siano (1) Dovrei provare a consegnare questo lavoro a un altro thread? la mia chiamata a '' '_newsFeedService.GetNewsFeedItemsForUser (userId)' '' è una chiamata a un database, che suppongo sia di rete e I/O, non di CPU. (2) Se è una buona idea, come dovrebbe essere scritto quel codice? Tutti gli esempi che vedo non mostrano quel bit del codice. Qualsiasi esempio sarebbe davvero utile. –

+1

@SimonLomax Basta cercare "C# entity framework async" - https://msdn.microsoft.com/en-us/data/jj819165.aspx se si utilizza il framework di entità o "database asincrono C#" -http: // www. tugberkugurlu.com/archive/asynchronous-database-calls-with-task-based-asynchronous-programming-model-tap-in-asp-net-mvc-4 se si utilizza ADO standard. –

+1

In realtà sto usando mongo e il driver C# per mongo, non Entity Framework. A prescindere da ciò, da quello che dici, a meno che le chiamate che sto facendo dal mio metodo personalizzato '' '_newsFeedService.GetNewsFeedItemsForUser (userId);' '' sono asincrone quindi non ha senso cercare di rendere il mio metodo di azione del controller asincrono. –