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 |
---|---|---|
Output non deterministico per operazioni non associative o non commutative |
Output non deterministico per operazioni non associative o non commutative |
|
Non applicabile |
Non applicabile |
|
Non applicabile |
Non applicabile |
|
Non applicabile |
Non applicabile |
|
Output non deterministico per operazioni non associative o non commutative |
Output non deterministico per operazioni non associative o non commutative |
|
Risultati ordinati |
Risultati non ordinati |
|
Risultati ordinati |
Risultati non ordinati |
|
Non applicabile |
Non applicabile |
|
Non applicabile |
Non applicabile |
|
Risultati ordinati |
Risultati non ordinati |
|
Restituisce l'elemento specificato |
Elemento arbitrario |
|
Restituisce l'elemento specificato |
Elemento arbitrario |
|
Risultati non ordinati |
Risultati non ordinati |
|
Restituisce l'elemento specificato |
Elemento arbitrario |
|
Restituisce l'elemento specificato |
Elemento arbitrario |
|
Viene eseguita in modo non deterministico e in parallelo |
Viene eseguita in modo non deterministico e in parallelo |
|
Risultati ordinati |
Risultati non ordinati |
|
Risultati ordinati |
Risultati non ordinati |
|
Risultati ordinati |
Risultati non ordinati |
|
Risultati ordinati |
Risultati non ordinati |
|
Restituisce l'elemento specificato |
Elemento arbitrario |
|
Restituisce l'elemento specificato |
Elemento arbitrario |
|
Non applicabile |
Non applicabile |
|
Non applicabile |
Non applicabile |
|
Riordina la sequenza |
Inizia una nuova sezione ordinata |
|
Riordina la sequenza |
Inizia una nuova sezione ordinata |
|
Non applicabile (stessa impostazione predefinita di AsParallel) |
Non applicabile |
|
Non applicabile (stessa impostazione predefinita di AsParallel) |
Non applicabile |
|
Inverte |
Nessuna operazione |
|
Risultati ordinati |
Risultati non ordinati |
|
Select (indicizzato) |
Risultati ordinati |
Risultati non ordinati |
Risultati ordinati |
Risultati non ordinati |
|
SelectMany (indicizzato) |
Risultati ordinati |
Risultati non ordinati |
Confronto ordinato |
Confronto non ordinato |
|
Non applicabile |
Non applicabile |
|
Non applicabile |
Non applicabile |
|
Vengono ignorati i primi n elementi |
Vengono ignorati n elementi qualsiasi |
|
Risultati ordinati |
Non deterministico Esegue SkipWhile sull'ordine arbitrario corrente |
|
Output non deterministico per operazioni non associative o non commutative |
Output non deterministico per operazioni non associative o non commutative |
|
Accetta i primi n elementi |
Accetta n elementi qualsiasi |
|
Risultati ordinati |
Non deterministico Esegue TakeWhile sull'ordine arbitrario corrente |
|
Integra OrderBy |
Integra OrderBy |
|
Integra OrderBy |
Integra OrderBy |
|
Risultati ordinati |
Risultati non ordinati |
|
Non applicabile |
Non applicabile |
|
Risultati ordinati |
Risultati non ordinati |
|
Risultati ordinati |
Risultati non ordinati |
|
Risultati ordinati |
Risultati non ordinati |
|
Risultati ordinati |
Risultati non ordinati |
|
Where (indicizzato) |
Risultati ordinati |
Risultati non ordinati |
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.