Compartilhar via


Preservação da ordem em PLINQ

No PLINQ, a meta é maximizar o desempenho e manter a correção. Uma consulta deve ser executada o mais rápido possível, mas ainda produzir os resultados corretos. Em alguns casos, a correção requer que a ordem da sequência de origem seja preservada; no entanto, a ordenação pode ser computacionalmente cara. Portanto, por padrão, o PLINQ não preserva a ordem da sequência de origem. Nesse sentido, o PLINQ é semelhante ao LINQ to SQL, mas é diferente do LINQ to Objetos, que preserva a ordenação.

Para substituir o comportamento padrão, você pode ativar a preservação da ordem usando o AsOrdered operador na sequência de origem. Em seguida, você pode desativar a preservação da ordem posteriormente na consulta usando o AsUnordered método. Com ambos os métodos, a consulta é processada com base na heurística que determina se a consulta deve ser executada como paralela ou sequencial. Para obter mais informações, consulte Noções básicas sobre a aceleração no 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 forma alguma.

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 produz as primeiras 1000 cidades na sequência de origem que atendem à condição, mas sim alguns conjuntos de 1000 cidades que atendem à condição. Os 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 passados para o próximo estágio do processamento da consulta em uma ordem arbitrária. Além disso, uma partição pode produzir um subconjunto de seus resultados antes de continuar processando os elementos restantes. A ordem resultante pode ser diferente a cada vez. Seu aplicativo não pode controlar isso porque depende de como o sistema operacional agenda os threads.

O exemplo a seguir substitui o comportamento padrão usando o AsOrdered operador na sequência de origem. Isso garante que o Take método retorne as primeiras 1000 cidades na sequência de origem que atendem à 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 porque deve acompanhar a ordenação original em todas as partições e, em tempo de mesclagem, garantir que a ordenação seja consistente. Portanto, recomendamos que você use AsOrdered somente quando for necessário e somente para as partes da consulta que exigem isso. Quando a preservação da ordem não for mais necessária, use AsUnordered para desativá-la. O exemplo a seguir consegue 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 que impõem ordem para o restante da consulta. Em outras palavras, 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 de origem ordenadas para produzir resultados corretos:

Alguns operadores de consulta PLINQ se comportam de forma diferente, dependendo se a sequência de origem é ordenada ou não ordenada. 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 determinísticas ou não comunicativas Saída não determinística para operações não determinísticas ou não comunicativas
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 determinísticas ou não comunicativas Saída não determinística para operações não determinísticas ou não comunicativas
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 Retornar elemento especificado Elemento arbitrário
ElementAtOrDefault Retornar elemento especificado Elemento arbitrário
Except Resultados não ordenados Resultados não ordenados
First Retornar elemento especificado Elemento arbitrário
FirstOrDefault Retornar 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 Retornar elemento especificado Elemento arbitrário
LastOrDefault Retornar 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 (o mesmo padrão que AsParallel ) Não aplicável
Repeat Não aplicável (o mesmo padrão que AsParallel) Não aplicável
Reverse Inverte Não faz nada
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 quaisquer 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 determinísticas ou não comunicativas Saída não determinística para operações não determinísticas ou não comunicativas
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

Resultados não ordenados não são reordenados; eles simplesmente não têm nenhum critério especial de ordenação aplicado a eles. Em alguns casos, uma consulta não ordenada pode manter a ordenação da sequência de origem. Para consultas que usam o operador Select indexado, o PLINQ garante que os elementos de saída serão lançados na ordem do aumento dos índices, mas não garante quais índices serão atribuídos a quais elementos.

Consulte também