7

Ho letto tutte le domande correlate in SO, ma un po 'confuso sull'approccio migliore per il mio scenario in cui vengono attivate più chiamate al servizio web.Task.StartNew() vs Parallel.ForEach: scenario Richieste Web multiple

Ho un servizio di aggregazione che prende un input, lo analizza e lo traduce in più richieste web, effettua le chiamate di richiesta web (non correlate, quindi potrebbero essere attivate in parallelo) e consolida la risposta che viene inviata al chiamante. Il codice seguente viene utilizzato in questo momento -

list.ForEach((object obj) => 
{ 
    tasks.Add(Task.Factory.StartNew((object state) => 
    { 
      this.ProcessRequest(obj); 
    }, obj, CancellationToken.None, TaskCreationOptions.AttachedToParent, TaskScheduler.Default)); 
}); 
await Task.WhenAll(tasks); 

il await Task.WhenAll(tasks) viene da Scott Hanselman di post in cui si dice che

"Una soluzione migliore dal punto di vista di scalabilità, dice Stefano, è quello di approfittare di I/O asincrono Quando si chiama attraverso la rete , non c'è alcuna ragione (diversa dalla convenienza) per bloccare i thread mentre si attende che la risposta torni "

Il codice esistente sembra consumare troppi thread e il tempo processore aumenta fino al 100% sul carico di produzione e questo mi fa riflettere.

L'altra alternativa è utilizzare Parallel.ForEach che utilizza un partizionatore ma anche "blocca" la chiamata, che va bene per il mio scenario.

Considerando che questo è tutto lavoro "Async IO" e non "CPU bound", e le richieste web non sono lunghe (ritorno in max 3 secondi), tendo a credere che il codice esistente sia abbastanza buono. Ma questo fornirebbe un throughput migliore di Parallel.ForEach? Parallel.ForEach probabilmente utilizza il numero "minimo" di Attività a causa del partizionamento e quindi dell'uso ottimale dei thread (?). Ho testato Parallel.ForEach con alcuni test locali e non sembra essere migliore.

L'obiettivo è ridurre il tempo di CPU e aumentare il throughput e quindi una migliore scalabilità. Esiste un approccio migliore per la gestione delle richieste Web in parallelo?

Apprezzare qualsiasi input, grazie.

EDIT: metodo ProcessRequest illustrato nel codice di esempio utilizza infatti HttpClient e dei suoi metodi asincroni al fuoco richieste (PostAsync, GetAsync, PutAsync).

+1

Se 'ProcessRequest' utilizza metodi asincroni, perché sono voi calli lo trovi all'interno di 'Task.Factory.StartNew'? Potresti semplicemente aggiungere l'attività che ritorna alla tua lista. Se stai bloccando al suo interno, non importa che tu usi metodi asincroni in parti di esso. L'ultima chiamata di blocco annulla qualsiasi beneficio –

+0

"diverso dalla convenienza", beh, questa è una buona ragione. – usr

risposta

5

rende le chiamate di richiesta web (non collegati, in modo da potrebbero essere licenziati in parallelo)

cosa si vuole realmente è chiamarli concomitanza, non in parallelamente. Cioè, "allo stesso tempo", non "usando più thread".

il codice esistente sembra consumare troppi thread

Sì, lo penso anche io. :)

Considerando questo è tutto il lavoro "IO asincrono" e non "CPU bound" funziona

allora dovrebbe essere fatto tutto in modo asincrono, e non utilizzando il parallelismo compito o altro codice parallelo.

Come Antii sottolineato, si dovrebbe rendere il codice asincrono asincrono:

public async Task ProcessRequestAsync(...); 

Allora che cosa si vuole fare è consumare utilizzando concorrenza asincrono (Task.WhenAll), non concorrenza parallela (StartNew/Run/Parallel):

await Task.WhenAll(list.Select(x => ProcessRequestAsync(x))); 
+0

Paralleli e simultanei sono sinonimi. Quando si utilizza "parallelo" in questa risposta, sembra che si intendesse "multithread". 'Quindi dovrebbe essere fatto in modo asincrono e non usando TPL o codice parallelo. Non dovrebbe usare' StartNew' o 'Run' del TPL; usare la TPL per gestire le attività che rappresentano il lavoro asincrono andrebbe bene, poiché è in effetti ciò che hai mostrato. Non stai "non usando la TPL", la stai solo usando in modo diverso .. – Servy

+1

Non concorda con la terminologia "parallela" e "concorrente". Ma tu hai ragione su TPL; Intendevo dire "parallelismo dei compiti". –

+0

Fare cose in parallelo sta facendo più cose allo stesso tempo. È possibile eseguire più operazioni contemporaneamente mediante l'utilizzo di più thread o eseguendo più operazioni intrinseche asincrone allo stesso tempo. Entrambe le operazioni portano al parallelismo. La 'Parallel' * class * in .NET ha operazioni che implicano tutte il multithreading e non altri mezzi per realizzare il parallelismo, ma il concetto generale di" paralleism "o" fare questo in parallelo "non è in alcun modo specifico per più thread. Cosa ti farebbe pensare che sarebbe? – Servy

0

Il wrapping di chiamate sincrone all'interno di Task.Factory.StartNew non offre alcun vantaggio asincrono. È necessario utilizzare le funzioni asincrone appropriate per una migliore scalabilità. Nota come Scott Hanselman fa funzioni asincrone in post a cui ti riferisci.

Per esempio

public async Task<bool> ValidateUrlAsync(string url) 
{ 
    using(var response = (HttpWebResponse)await WebRequest.Create(url).GetResponseAsync()) 
    return response.StatusCode == HttpStatusCode.Ok; 
} 

Checkout http://blogs.msdn.com/b/pfxteam/archive/2012/03/24/10287244.aspx

Quindi, il metodo ProcessRequest dovrebbe essere implementato come asincrone come

public async Task<bool> ProcessRequestAsync(...) 

allora si può solo

tasks.Add(this.ProcessRequestAsync(obj)) 

Se si avvia l'attività con Task.Factory.StartNew non funziona come asincrono anche se il metodo ProcessRequest esegue internamente chiamate asincrone. Se si vuole utilizzare Task.Factory si dovrebbe rendere il vostro lambda anche asincrona come:

tasks.Add(Task.Factory.StartNew(async (object state) => 
{ 
    await this.ProcessRequestAsync(obj); 
}, obj, CancellationToken.None, TaskCreationOptions.AttachedToParent, TaskScheduler.Default)); 
+0

Probabilmente mi sono perso per citare ... in realtà, l'invocazione ProcessRequest effettua chiamate a versioni asincrone dell'API HttpClient - PostAsync, SendAsync e GetAsync in base alla richiesta (obj) inoltrata. Aggiungerà la domanda. – Lalman

+0

Aggiungi solo funzioni asincrone all'elenco delle attività. Non utilizzare Task.Factory.StartNew. –

+0

È collegato alla CPU. Async I/O non fornirà più throughput. – usr

3

Se siete CPU bound (sei - "Tempo processore spara fino al 100%"), è necessario ridurre l'utilizzo della CPU. L'IO asincrono non aiuta in questo. Se qualcosa provoca un po 'più di utilizzo della CPU (non visibile qui).

Profilo l'app per vedere ciò che richiede così tanto tempo CPU e ottimizzare quel codice.

Il modo in cui si avvia il parallelismo (Parallel, Task, async IO) non fa nulla per l'efficienza dell'azione parallela stessa. La rete non diventa più veloce se la chiami in modo asincrono. È ancora lo stesso hardware. Inoltre, non meno l'utilizzo della CPU.

Determinare il grado ottimale di parallelismo sperimentalmente e scegliere una tecnica di parallelismo adatta per quel grado. Se sono poche decine di thread sono assolutamente soddisfacenti. Se è nelle centinaia prendere in seria considerazione l'IO asincrono.