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.

Vedi anche