Condividi tramite


Conservazione dell'ordine in PLINQ

In PLINQ, l'obiettivo è ottimizzare le prestazioni senza tuttavia compromettere la correttezza. Una query deve essere eseguita nel modo più veloce possibile e comunque produrre risultati corretti. In alcuni casi la correttezza richiede la conservazione dell'ordine della sequenza di origine. Tuttavia, l'ordinamento può essere oneroso dal punto di vista del calcolo. Pertanto, per impostazione predefinita, PLINQ non conserva l'ordine della sequenza di origine. Sotto questo aspetto PLINQ assomiglia a LINQ to SQL, ma differisce da LINQ to Objects, che invece conserva l'ordine.

Per eseguire l'override del comportamento predefinito è possibile attivare la conservazione dell'ordine tramite l'operatore AsOrdered nella sequenza di origine. La conservazione dell'ordine può quindi essere disattivata nella query in un secondo momento tramite il metodo AsUnordered<TSource>. Con entrambi i metodi, la domanda viene elaborata in base alle regole euristiche che determinano se l'esecuzione della query avverrà in modalità parallela o sequenziale. Per ulteriori informazioni, vedere Informazioni sull'aumento di velocità in PLINQ.

Nell'esempio seguente viene mostrata una query parallela non ordinata che filtra tutti gli elementi che soddisfano una condizione, senza tentare di ordinare i risultati in alcun modo.

Dim cityQuery = From city In cities.AsParallel()
               Where City.Population > 10000
               Take (1000)
var cityQuery = (from city in cities.AsParallel()
                 where city.Population > 10000
                 select city)
                   .Take(1000);

Questa query non produce necessariamente le prime 1000 città nella sequenza di origine che soddisfano la condizione, ma piuttosto un set di 1000 città che la soddisfano. Gli operatori di query PLINQ eseguono il partizionamento della sequenza di origine in più sottosequenze che vengono elaborate come attività simultanee. Se non è stata specificata la conservazione dell'ordine, i risultati di ogni partizione vengono passati alla fase successiva della query in un ordine arbitrario. Inoltre, una partizione può produrre un subset dei propri risultati prima di continuare a elaborare gli elementi restanti. È possibile che l'ordine risultante sia diverso ogni volta. L'applicazione non può controllare questo comportamento perché dipende da come il sistema operativo pianifica i thread.

Nell'esempio seguente viene eseguito l'override del comportamento predefinito tramite l'operatore AsOrdered nella sequenza di origine. In questo modo si garantisce che il metodo Take<TSource> restituisca le prime 10 città nella sequenza di origine che soddisfano la condizione.

Dim orderedCities = From city In cities.AsParallel().AsOrdered()
                    Where City.Population > 10000
                    Take (1000)
            var orderedCities = (from city in cities.AsParallel().AsOrdered()
                                 where city.Population > 10000
                                 select city)
                                .Take(1000);

Tuttavia, questa query probabilmente presenta una velocità di esecuzione minore rispetto alla versione non ordinata. Infatti, deve tenere traccia dell'ordine originale in tutte le partizioni e, al momento dell'unione, garantire che l'ordine sia coerente. Pertanto, è consigliabile utilizzare AsOrdered solo quando occorre e solo per quelle parti della query che lo richiedono. Quando la conservazione dell'ordine non è più necessaria, utilizzare AsUnordered<TSource> per disattivarla. Nell'esempio seguente ciò si ottiene mediante la composizione di due query.

        Dim orderedCities2 = From city In cities.AsParallel().AsOrdered()
                             Where city.Population > 10000
                             Select city
                             Take (1000)

        Dim finalResult = From city In orderedCities2.AsUnordered()
                            Join p In people.AsParallel() On city.Name Equals p.CityName
                            Select New With {.Name = city.Name, .Pop = city.Population, .Mayor = city.Mayor}

        For Each city In finalResult
            Console.WriteLine(city.Name & ":" & city.Pop & ":" & city.Mayor)
        Next

var orderedCities2 = (from city in cities.AsParallel().AsOrdered()
                      where city.Population > 10000
                      select city)
                        .Take(1000);


var finalResult = from city in orderedCities2.AsUnordered()
                  join p in people.AsParallel() on city.Name equals p.CityName into details
                  from c in details
                  select new { Name = city.Name, Pop = city.Population, Mayor = c.Mayor };

foreach (var city in finalResult) { /*...*/ }

Notare che PLINQ conserva per il resto della query l'ordine di una sequenza prodotta da operatori che impongono l'ordinamento. In altre parole, gli operatori quali OrderBy e ThenBy vengono trattati come se fossero seguiti da una chiamata a AsOrdered.

Operatori di query e ordinamento

Gli operatori di query seguenti introducono la conservazione dell'ordine in tutte le operazioni successive in una query oppure finché non si chiama AsUnordered<TSource>:

Gli operatori di query PLINQ seguenti in alcuni casi possono richiedere la produzione di risultati corretti da parte di sequenze di origine ordinate:

Alcuni operatori di query PLINQ si comportano in modo diverso, a seconda che la sequenza di origine sia ordinata o non ordinata. Questi operatori vengono elencati nella tabella seguente.

Operatore

Risultato quando la sequenza di origine è ordinata

Risultato quando la sequenza di origine non è ordinata

Aggregate

Output non deterministico per operazioni non associative o non commutative

Output non deterministico per operazioni non associative o non commutative

All<TSource>

Non applicabile

Non applicabile

Any

Non applicabile

Non applicabile

AsEnumerable<TSource>

Non applicabile

Non applicabile

Average

Output non deterministico per operazioni non associative o non commutative

Output non deterministico per operazioni non associative o non commutative

Cast<TResult>

Risultati ordinati

Risultati non ordinati

Concat

Risultati ordinati

Risultati non ordinati

Count

Non applicabile

Non applicabile

DefaultIfEmpty

Non applicabile

Non applicabile

Distinct

Risultati ordinati

Risultati non ordinati

ElementAt<TSource>

Restituisce l'elemento specificato

Elemento arbitrario

ElementAtOrDefault<TSource>

Restituisce l'elemento specificato

Elemento arbitrario

Except

Risultati non ordinati

Risultati non ordinati

First

Restituisce l'elemento specificato

Elemento arbitrario

FirstOrDefault

Restituisce l'elemento specificato

Elemento arbitrario

ForAll<TSource>

Viene eseguita in modo non deterministico e in parallelo

Viene eseguita in modo non deterministico e in parallelo

GroupBy

Risultati ordinati

Risultati non ordinati

GroupJoin

Risultati ordinati

Risultati non ordinati

Intersect

Risultati ordinati

Risultati non ordinati

Join

Risultati ordinati

Risultati non ordinati

Last

Restituisce l'elemento specificato

Elemento arbitrario

LastOrDefault

Restituisce l'elemento specificato

Elemento arbitrario

LongCount

Non applicabile

Non applicabile

Min

Non applicabile

Non applicabile

OrderBy

Riordina la sequenza

Inizia una nuova sezione ordinata

OrderByDescending

Riordina la sequenza

Inizia una nuova sezione ordinata

Range

Non applicabile (stessa impostazione predefinita di AsParallel)

Non applicabile

Repeat<TResult>

Non applicabile (stessa impostazione predefinita di AsParallel)

Non applicabile

Reverse<TSource>

Inverte

Nessuna operazione

Select

Risultati ordinati

Risultati non ordinati

Select (indicizzato)

Risultati ordinati

Risultati non ordinati

SelectMany

Risultati ordinati

Risultati non ordinati

SelectMany (indicizzato)

Risultati ordinati

Risultati non ordinati

SequenceEqual

Confronto ordinato

Confronto non ordinato

Single

Non applicabile

Non applicabile

SingleOrDefault

Non applicabile

Non applicabile

Skip<TSource>

Vengono ignorati i primi n elementi

Vengono ignorati n elementi qualsiasi

SkipWhile

Risultati ordinati

Non deterministico Esegue SkipWhile sull'ordine arbitrario corrente

Sum

Output non deterministico per operazioni non associative o non commutative

Output non deterministico per operazioni non associative o non commutative

Take<TSource>

Accetta i primi n elementi

Accetta n elementi qualsiasi

TakeWhile

Risultati ordinati

Non deterministico Esegue TakeWhile sull'ordine arbitrario corrente

ThenBy

Integra OrderBy

Integra OrderBy

ThenByDescending

Integra OrderBy

Integra OrderBy

ToTSource[]

Risultati ordinati

Risultati non ordinati

ToDictionary

Non applicabile

Non applicabile

ToList<TSource>

Risultati ordinati

Risultati non ordinati

ToLookup

Risultati ordinati

Risultati non ordinati

Union

Risultati ordinati

Risultati non ordinati

Where

Risultati ordinati

Risultati non ordinati

Where (indicizzato)

Risultati ordinati

Risultati non ordinati

Zip

Risultati ordinati

Risultati non ordinati

I risultati non ordinati non vengono attivamente riordinati in sequenza casuale. Semplicemente a tali risultati non viene applicata una specifica logica di ordinamento. In alcuni casi, una query non ordinata può mantenere l'ordinamento della sequenza di origine. Per le query che utilizzano l'operatore Select indicizzato, PLINQ garantisce che gli elementi di output saranno riprodotti nell'ordine degli indici incrementali, ma non fornisce alcuna garanzia su quali indici verranno assegnati agli elementi.

Vedere anche

Concetti

Parallel LINQ (PLINQ)

Programmazione parallela in .NET Framework