2009-03-04 11 views
9

sto usando Parallel LINQ, e sto cercando di scaricare molti URL contemporaneamente utilizzando essentily codice come questo:Parallel LINQ - utilizzare più thread rispetto ai processori (per i non-CPU compiti legati)

int threads = 10; 
Dictionary<string, string> results = urls.AsParallel(threads).ToDictionary(url => url, url => GetPage(url); 

Dal il download delle pagine Web è limitato dalla rete anziché dalla CPU, utilizzando più thread del mio numero di processori/core è molto positivo, poiché la maggior parte del tempo in ciascun thread è trascorsa in attesa che la rete raggiunga il livello. Tuttavia, a giudicare dal fatto che l'esecuzione di quanto sopra con threads = 2 ha le stesse prestazioni di threads = 10 sulla mia macchina dual core, sto pensando che le pedate inviate ad AsParallel siano limitate al numero di core.

Esiste un modo per ignorare questo comportamento? C'è una libreria simile disponibile che non ha questa limitazione?

(ho trovato una libreria per Python, ma hanno bisogno di qualcosa che funziona in .Net)

risposta

12

fare gli URL fanno riferimento allo stesso server? In tal caso, potrebbe essere che stai vincendo il limite della connessione HTTP invece del limite di threading. C'è un modo semplice per dire - modificare il codice per:

int threads = 10; 
Dictionary<string, string> results = urls.AsParallel(threads) 
    .ToDictionary(url => url, 
        url => { 
         Console.WriteLine("On thread {0}", 
             Thread.CurrentThread.ManagedThreadId); 
         return GetPage(url); 
        }); 

EDIT: Hmm. Non riesco a ottenere ToDictionary() in parallelo a su tutti con un po 'di codice di esempio. Funziona bene per Select(url => GetPage(url)) ma non ToDictionary. Cercheremo un po '.

MODIFICA: Ok, non riesco ancora a ottenere ToDictionary in parallelo, ma è possibile aggirare il problema. Ecco un breve ma completo programma:

using System; 
using System.Collections.Generic; 
using System.Threading; 
using System.Linq; 
using System.Linq.Parallel; 

public class Test 
{ 

    static void Main() 
    { 
     var urls = Enumerable.Range(0, 100).Select(i => i.ToString()); 

     int threads = 10; 
     Dictionary<string, string> results = urls.AsParallel(threads) 
      .Select(url => new { Url=url, Page=GetPage(url) }) 
      .ToDictionary(x => x.Url, x => x.Page); 
    } 

    static string GetPage(string x) 
    { 
     Console.WriteLine("On thread {0} getting {1}", 
          Thread.CurrentThread.ManagedThreadId, x); 
     Thread.Sleep(2000); 
     return x; 
    } 
} 

Quindi, quanti thread utilizza? 5. Perché? Chissà. Ho 2 processori, quindi non è così - e abbiamo specificato 10 thread, quindi non è così. Usa ancora 5 anche se cambio GetPage per martellare la CPU.

Se si ha solo bisogno di utilizzare questo per un compito particolare - e non ti dispiace codice leggermente maleodorante - potrebbe essere meglio implementarlo da soli, per essere onesti.

+0

sto ottenendo lo stesso sintomo. Ho eseguito la tua analisi e ho ottenuto solo 1 thread .. credo che l'aumento delle prestazioni da 1 a 2 thread che ho visto era nella mia testa –

+0

@DrFredEdison: Quindi cosa succede se si utilizza il modulo Select/ToDictionary come nell'esempio, invece? –

+0

Sto vedendo praticamente lo stesso risultato di te. Ricevo circa 5 thread utilizzati per ogni prova ora. Grazie per avermi fatto arrivare così lontano ... Penso che per il momento avrò lavoro per quello che mi serve. –

0

Controlla il tuo traffico di rete. Se gli URL provengono dallo stesso dominio, potrebbe limitare la larghezza di banda. Altre connessioni potrebbero non fornire alcuna accelerazione.

6

Per impostazione predefinita, .Net ha il limite di 2 connessioni simultanee a un punto di servizio finale (IP: porta). Ecco perché non si vedrebbe una differenza se tutti gli URL fossero sullo stesso server.

Può essere controllato utilizzando la proprietà ServicePointManager.DefaultPersistentConnectionLimit.

1

Penso che ci siano già buone risposte alla domanda, ma mi piacerebbe fare un punto importante. L'uso di PLINQ per compiti che non sono vincolati alla CPU è in linea di principio sbagliato. Per non dire che non funzionerà - lo farà, ma l'uso di più thread quando non è necessario può causare problemi.

Purtroppo, non c'è un buon modo per risolvere questo problema in C#. In F # è possibile utilizzare flussi di lavoro asincroni eseguiti in parallelo, ma non bloccare il thread quando si eseguono chiamate asincrone (sotto la copertina, utilizza i metodi BeginOperation e EndOperation).E 'possibile trovare maggiori informazioni qui:

La stessa idea può in qualche misura essere utilizzato in C#, ma sembra un po' strano (ma è più efficiente). Ho scritto un articolo su questo e c'è anche una libreria che dovrebbe essere un po 'più evoluto che la mia idea originale:

Problemi correlati