Conservazione dell'ordine in PLINQ
In PLINQ, l'obiettivo è ottimizzare le prestazioni, mantenendo la correttezza. Una query deve essere eseguita il più velocemente possibile ma comunque produrre i risultati corretti. In alcuni casi, la correttezza richiede il mantenimento dell'ordine della sequenza di origine, ma l'ordinamento può essere oneroso a livello di risorse di calcolo. Pertanto, per impostazione predefinita, PLINQ non mantiene l'ordine della sequenza di origine. Da questo punto di vista, PLINQ è simile a LINQ to SQL, ma differisce da LINQ to Objects, che mantiene l'ordinamento.
Per eseguire l'override del comportamento predefinito, è possibile attivare il mantenimento dell'ordine usando l'operatore AsOrdered nella sequenza di origine. È quindi possibile disattivare il mantenimento dell'ordine in un secondo momento nella query usando il metodo AsUnordered. Con entrambi i metodi, la query viene elaborata in base alle regole euristiche che determinano se scegliere l'esecuzione parallela o sequenziale per la query. Per altre informazioni, vedere Informazioni sull'aumento di velocità in PLINQ.
L'esempio seguente mostra una query parallela non ordinata che applica un filtro per individuare tutti gli elementi che soddisfano una condizione, senza tentare di ordinare i risultati in alcun modo.
var cityQuery =
(from city in cities.AsParallel()
where city.Population > 10000
select city).Take(1000);
Dim cityQuery = From city In cities.AsParallel()
Where city.Population > 10000
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 soddisfano la condizione. Gli operatori di query PLINQ partizionano la sequenza di origine in più sottosequenze che vengono elaborate come attività simultanee. Se non viene specificato di mantenere l'ordine, i risultati di ogni partizione vengono passati alla fase successiva della query in ordine arbitrario. Inoltre, una partizione potrebbe produrre un subset di risultati prima di continuare a elaborare gli elementi rimanenti. L'ordine risultante potrebbe essere diverso ogni volta. L'applicazione non può controllare questo comportamento perché dipende dalla modalità di pianificazione dei thread del sistema operativo.
Nell'esempio seguente viene eseguito l'override del comportamento predefinito tramite l'operatore AsOrdered nella sequenza di origine. Ciò assicura che il metodo Take restituisca le prime 1000 città nella sequenza di origine che soddisfano la condizione.
var orderedCities =
(from city in cities.AsParallel().AsOrdered()
where city.Population > 10000
select city).Take(1000);
Dim orderedCities = From city In cities.AsParallel().AsOrdered()
Where city.Population > 10000
Take (1000)
Tuttavia, questa query probabilmente non viene eseguita altrettanto velocemente della versione non ordinata perché deve tenere traccia dell'ordine originale per tutte le partizioni e assicurarsi che l'ordine sia coerente al momento dell'unione. È pertanto consigliabile usare AsOrdered solo quando è necessario e solo per le parti della query che lo richiedono. Quando non occorre più mantenere l'ordine, usare AsUnordered per disattivare questo comportamento. L'esempio ottiene questo risultato tramite la composizione di due query.
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
{
city.Name,
Pop = city.Population,
c.Mayor
};
foreach (var city in finalResult) { /*...*/ }
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
Si noti che PLINQ mantiene l'ordinamento di una sequenza da operatori che impongono l'ordine per il resto della query. In altre parole, gli operatori come OrderBy e ThenBy vengono gestiti come se fossero seguiti da una chiamata a AsOrdered.
Operatori di query e di ordinamento
Gli operatori di query seguenti introducono il mantenimento dell'ordine in tutte le successive operazioni in una query o fino a quando non viene chiamato AsUnordered:
Gli operatori di query PLINQ seguenti possono richiedere in alcuni casi sequenze di origine ordinate per produrre risultati corretti:
Alcuni operatori di query PLINQ si comportano in modo diverso, a seconda che la sequenza di origine sia ordinata o non ordinata. Questi operatori sono 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 | Non applicabile | Non applicabile |
Any | Non applicabile | Non applicabile |
AsEnumerable | 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 | 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 | Restituisce l'elemento specificato | Elemento arbitrario |
ElementAtOrDefault | 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 | Esecuzione non deterministica in parallelo | Esecuzione non deterministica 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 | Avvia una nuova sezione ordinata |
OrderByDescending | Riordina la sequenza | Avvia una nuova sezione ordinata |
Range | Non applicabile (stessa impostazione predefinita di AsParallel) | Non applicabile |
Repeat | Non applicabile (stessa impostazione predefinita di AsParallel) | Non applicabile |
Reverse | Inverte | Non effettua alcuna 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 | Ignora i primi elementi n | Ignora tutti gli elementi n |
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 | Accetta i primi n elementi |
Accetta n elementi |
TakeWhile | Risultati ordinati | Non deterministico Esegue TakeWhile sull'ordine arbitrario corrente |
ThenBy | Supplemento di OrderBy |
Supplemento di OrderBy |
ThenByDescending | Supplemento di OrderBy |
Supplemento di OrderBy |
ToArray | Risultati ordinati | Risultati non ordinati |
ToDictionary | Non applicabile | Non applicabile |
ToList | 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 disposti attivamente in ordine casuale, ma semplicemente non viene loro applicata alcuna logica speciale di ordinamento. In alcuni casi, una query non ordinata può mantenere l'ordine della sequenza di origine. Per le query che usano l'operatore Select indicizzato, PLINQ garantisce che gli elementi di output vengano restituiti nell'ordine crescente di indice, ma non viene in alcun modo garantito quali indici verranno assegnati a quali elementi.