Compartilhar via


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.

Confira também