Preservação da ordem em PLINQ
Em PLINQ, o objetivo é maximizar o desempenho mantendo a exatidão. Uma consulta deve ser executada o mais rápido possível, mas ainda produzir os resultados corretos. Em alguns casos, a exatidão requer que a ordem da sequência de origem seja preservada. No entanto, a ordenação pode ser dispendiosa. Portanto, por padrão, o PLINQ não preserva a ordem da sequência de origem. Nesse sentido, o PLINQ assemelha-se ao LINQ to SQL, mas é diferente do LINQ to Objects, que preserva a ordenação.
Para substituir o comportamento padrão, ative a preservação da ordem usando o operador AsOrdered na sequência de origem. Posteriormente, você pode desativar a preservação da ordem na consulta usando o método AsUnordered. Nos dois métodos, a consulta é processada com base na heurística que determina se a consulta deverá ser executada como paralela ou sequencial. Para saber mais, veja Noções básicas sobre agilização em PLINQ.
O exemplo a seguir mostra uma consulta paralela não ordenada que filtra todos os elementos que correspondem a uma condição, sem tentar ordenar os resultados de qualquer forma.
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)
Essa consulta não necessariamente gera as primeiras 1.000 cidades que atendem à condição na sequência de origem, mas em vez disso, gera alguns conjuntos de 1.000 cidades que atendem à condição. Operadores de consulta PLINQ particionam a sequência de origem em várias subsequências que são processadas como tarefas simultâneas. Se a preservação da ordem não for especificada, os resultados de cada partição serão enviados para a próxima fase da consulta em uma ordem arbitrária. Além disso, uma partição pode gerar um subconjunto de seus resultados antes de continuar a processar os elementos restantes. A ordem resultante pode ser sempre diferente. O aplicativo não pode controlar isso porque depende da forma como o sistema operacional agenda os threads.
O exemplo a seguir substitui o comportamento padrão usando o operador AsOrdered na sequência de origem. Isso garante que o método Take retornará as primeiras 1.000 cidades na sequência de origem que atendam à condição.
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)
No entanto, essa consulta provavelmente não é executada tão rápido quanto a versão não ordenada já que ela deve controlar a ordenação original em toda as partições e, no tempo de mesclagem, verificar se a ordenação é consistente. Portanto, é recomendável usar AsOrdered somente quando necessário e apenas para as partes da consulta que o exigem. Quando a preservação da ordem já não for necessária, use AsUnordered para desativá-la. O exemplo a seguir realiza isso compondo duas consultas.
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
Observe que o PLINQ preserva a ordenação de uma sequência produzida por operadores de imposição da ordem para o restante da consulta. Ou seja, operadores como OrderBy e ThenBy são tratados como se fossem seguidos por uma chamada para AsOrdered.
Operadores de consulta e ordenação
Os operadores de consulta a seguir apresentam a preservação da ordem em todas as demais operações de uma consulta ou até que AsUnordered seja chamado:
Os seguintes operadores de consulta PLINQ podem, em alguns casos, exigir sequências ordenadas de origem para produzir resultados corretos:
Alguns operadores de consulta PLINQ se comportam de forma diferente, dependendo se a sequência de origem for ordenada ou desordenada. A tabela a seguir lista esses operadores.
Operador | Resultado quando a sequência de origem é ordenada | Resultado quando a sequência de origem é desordenada |
---|---|---|
Aggregate | Saída não determinística para operações não associativas ou não comutativas | Saída não determinística para operações não associativas ou não comutativas |
All | Não aplicável | Não aplicável |
Any | Não aplicável | Não aplicável |
AsEnumerable | Não aplicável | Não aplicável |
Average | Saída não determinística para operações não associativas ou não comutativas | Saída não determinística para operações não associativas ou não comutativas |
Cast | Resultados ordenados | Resultados não ordenados |
Concat | Resultados ordenados | Resultados não ordenados |
Count | Não aplicável | Não aplicável |
DefaultIfEmpty | Não aplicável | Não aplicável |
Distinct | Resultados ordenados | Resultados não ordenados |
ElementAt | Retorna o elemento especificado | Elemento arbitrário |
ElementAtOrDefault | Retorna o elemento especificado | Elemento arbitrário |
Except | Resultados não ordenados | Resultados não ordenados |
First | Retorna o elemento especificado | Elemento arbitrário |
FirstOrDefault | Retorna o elemento especificado | Elemento arbitrário |
ForAll | Executa de forma não determinística em paralelo | Executa de forma não determinística em paralelo |
GroupBy | Resultados ordenados | Resultados não ordenados |
GroupJoin | Resultados ordenados | Resultados não ordenados |
Intersect | Resultados ordenados | Resultados não ordenados |
Join | Resultados ordenados | Resultados não ordenados |
Last | Retorna o elemento especificado | Elemento arbitrário |
LastOrDefault | Retorna o elemento especificado | Elemento arbitrário |
LongCount | Não aplicável | Não aplicável |
Min | Não aplicável | Não aplicável |
OrderBy | Reordena a sequência | Inicia nova seção ordenada |
OrderByDescending | Reordena a sequência | Inicia nova seção ordenada |
Range | Não aplicável (mesmo padrão de AsParallel) | Não aplicável |
Repeat | Não aplicável (mesmo padrão de AsParallel) | Não aplicável |
Reverse | Inverte | Não agir |
Select | Resultados ordenados | Resultados não ordenados |
Select (indexado) | Resultados ordenados | Resultados não ordenados. |
SelectMany | Resultados ordenados. | Resultados não ordenados |
SelectMany (indexado) | Resultados ordenados. | Resultados não ordenados. |
SequenceEqual | Comparação ordenada | Comparação não ordenada |
Single | Não aplicável | Não aplicável |
SingleOrDefault | Não aplicável | Não aplicável |
Skip | Ignora os primeiros n elementos | Ignora os n elementos |
SkipWhile | Resultados ordenados. | Não determinístico. Executa SkipWhile na ordem arbitrária atual |
Sum | Saída não determinística para operações não associativas ou não comutativas | Saída não determinística para operações não associativas ou não comutativas |
Take | Usa os primeiros n elementos |
Usa quaisquer n elementos |
TakeWhile | Resultados ordenados | Não determinístico. Executa TakeWhile na ordem arbitrária atual |
ThenBy | Suplementos OrderBy |
Suplementos OrderBy |
ThenByDescending | Suplementos OrderBy |
Suplementos OrderBy |
ToArray | Resultados ordenados | Resultados não ordenados |
ToDictionary | Não aplicável | Não aplicável |
ToList | Resultados ordenados | Resultados não ordenados |
ToLookup | Resultados ordenados | Resultados não ordenados |
Union | Resultados ordenados | Resultados não ordenados |
Where | Resultados ordenados | Resultados não ordenados |
Where (indexado) | Resultados ordenados | Resultados não ordenados |
Zip | Resultados ordenados | Resultados não ordenados |
Os resultados não ordenados não são ativamente embaralhados. Eles simplesmente não têm qualquer lógica de ordenação especial aplicada a eles. Em alguns casos, uma consulta não ordenada pode manter a ordenação da sequência de origem. No caso das consultas que usam o operador Select indexado, o PLINQ garante que os elementos de saída serão apresentados na ordem de índices crescentes, mas não garante quais índices serão atribuídos a quais elementos.