2016-02-28 20 views
9

Ho letto i documenti della libreria TPL e Task a copertura. Ma non riesco ancora a comprendere il seguente caso in modo molto chiaro e al momento ho bisogno di implementarlo.Esecuzione parallela per operazioni legate all'IO

semplificherò la mia situazione. Ho uno IEnumerable<Uri> di lunghezza 1000. Devo fare una richiesta per loro usando HttpClient.

Ho due domande.

  1. C'è non molto calcolo, solo in attesa di richiesta HTTP. In questo caso posso ancora usare Parallel.Foreach()?
  2. In caso di utilizzo di Task, invece, qual è la procedura migliore per crearne un numero enorme? Diciamo che uso Task.Factory.StartNew() e aggiungo quelle attività a un elenco e aspetto tutte. Esiste una funzionalità (come il partizionatore TPL) che controlla il numero massimo di attività e massimo HttpClient che posso creare?

Ci sono un paio di domande simili su SO, ma nessuno menziona il massimi. Il requisito è solo utilizzando le attività massime con HttpClient massimo.

Grazie in anticipo.

risposta

11

In questo caso è possibile utilizzare ancora Parallel.Foreach?

Questo non è proprio appropriato. Parallel.Foreach è più per lavoro intensivo della CPU. Inoltre non supporta le operazioni asincrone.

In caso di utilizzo di Task, invece, qual è la procedura migliore per crearne un numero enorme?

Utilizzare invece un blocco Dataflow TPL. Non si creano enormi quantità di attività che rimangono lì in attesa che un thread sia disponibile. È possibile configurare la quantità massima di attività e riutilizzarle per tutti gli elementi che nel frattempo siedono in un buffer in attesa di un'attività. Ad esempio:

var block = new ActionBlock<Uri>(
    uri => SendRequestAsync(uri), 
    new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 50 }); 

foreach (var uri in uris) 
{ 
    block.Post(uri); 
} 

block.Complete(); 
await block.Completion; 
+0

E se il numero di richieste simultanee supera il numero massimo di sistema operativo di richiesta http può fare? – ozgur

+0

@ozgur Dipende da dove è configurato quel limite. Ma se ce n'è uno allora assicurati di impostare 'MaxDegreeOfParallelism' su qualcosa di più basso di quello. – i3arnon

+0

Ultima domanda. L'esempio che hai fornito è adatto per le operazioni di I/O, ma non richiede il parallelismo della CPU? – ozgur

12

la risposta di i3arnon con TPL Dataflow è buona; Dataflow è utile soprattutto se si dispone di un mix di CPU e codice associato I/O. Risponderò al suo sentimento che lo Parallel è progettato per il codice associato alla CPU; non è la soluzione migliore per il codice basato su I/O e in particolare non appropriato per il codice asincrono.

Se si desidera una soluzione alternativa che funziona bene con la maggior parte-I Codice/O - e non richiede una libreria esterna - il metodo che stai cercando è Task.WhenAll:

var tasks = uris.Select(uri => SendRequestAsync(uri)).ToArray(); 
await Task.WhenAll(tasks); 

Questa è la soluzione più semplice, ma ha lo svantaggio di avviare tutte le richieste contemporaneamente. Soprattutto se tutte le richieste stanno per lo stesso servizio (o un piccolo insieme di servizi), questo può causare timeout. Per risolvere questo, è necessario utilizzare una sorta di strozzatura ...

Esiste una funzionalità (come il partizionatore TPL) che controlla il numero di attività massime e massimo HttpClient che posso creare?

TPL Dataflow ha quel bel MaxDegreeOfParallelism che inizia solo così tanti alla volta. È inoltre possibile limitare codice asincrono regolare utilizzando un altro incorporato, SemaphoreSlim:

private readonly SemaphoreSlim _sem = new SemaphoreSlim(50); 
private async Task SendRequestAsync(Uri uri) 
{ 
    await _sem.WaitAsync(); 
    try 
    { 
    ... 
    } 
    finally 
    { 
    _sem.Release(); 
    } 
} 

Nel caso di utilizzo di Task, invece, qual è la migliore pratica per la creazione di numero enorme di loro? Diciamo che uso Task.Factory.StartNew() e aggiungo quelle attività a un elenco e aspetto tutte.

In realtà non si desidera utilizzare StartNew. Ha solo un caso d'uso appropriato (parallelismo basato su attività dinamiche), che è estremamente raro. Il codice moderno dovrebbe usare Task.Run se devi spingere il lavoro su un thread in background. Ma non hai nemmeno bisogno di quello per cominciare, quindi né StartNewTask.Run è appropriato qui.

Ci sono un paio di domande simili su SO, ma nessuno menziona i massimi. Il requisito è solo utilizzando le attività massime con HttpClient massimo.

I massimi sono i casi in cui il codice asincrono diventa davvero complicato. Con il codice (parallelo) collegato alla CPU, la soluzione è ovvia: si utilizzano tanti thread quanti sono i core. (Bene, almeno è possibile avviare lì e regolare se necessario). Con il codice asincrono, non c'è una soluzione altrettanto ovvia. Dipende da molti fattori: quanta memoria hai, come risponde il server remoto (limitazione di velocità, timeout, ecc.)

Non ci sono soluzioni facili qui. Devi solo testare come la tua applicazione specifica si occupa di alti livelli di concorrenza, e quindi limitare ad un numero inferiore.


Ho alcuni slides for a talk che tenta di spiegare quando diverse tecnologie sono appropriati (parallelismo, asincronia, TPL flusso di dati, e Rx). Se si preferisce più di una descrizione scritta con ricette, penso che si possa beneficiare di my book sulla concorrenza.

+2

Quando hai detto che non esiste una soluzione facile, è finita la mia pena. Stavo pensando che probabilmente c'è un modo per farlo e ho cercato giorno e notte. Ora posso provare a implementare qualcosa di specifico per la mia situazione. Grazie mille. – ozgur