Preservação de Encomendas em PLINQ

No PLINQ, o objetivo é maximizar o desempenho, mantendo a correção. Uma consulta deve ser executada o mais rápido possível, mas ainda assim produzir os resultados corretos. Em alguns casos, a correção requer que a ordem da sequência de origem seja preservada; no entanto, encomendar pode ser computacionalmente caro. Portanto, por padrão, PLINQ não preserva a ordem da sequência de origem. A este respeito, PLINQ se assemelha ao LINQ to SQL, mas é diferente do LINQ to Objects, que preserva a ordenação.

Para substituir o comportamento padrão, você pode ativar a preservação de ordem usando o AsOrdered operador na sequência de origem. Em seguida, você pode desativar a preservação de 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 Understanding Speedup in 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)

Esta consulta não produz necessariamente as primeiras 1000 cidades na sequência de origem que cumprem a condição, mas sim algum conjunto de 1000 cidades que cumprem a 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 a próxima etapa da consulta em uma ordem arbitrária. Além disso, uma partição pode produzir um subconjunto de seus resultados antes de continuar a processar os elementos restantes. A ordem resultante pode ser diferente de 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 retorna 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 ordem original em todas as partições e, no momento da mesclagem, garantir que a ordem seja consistente. Portanto, recomendamos que você use AsOrdered somente quando for necessário e somente para as partes da consulta que o exigem. Quando a preservação do pedido não for mais necessária, use AsUnordered para desativá-lo. 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 PLINQ preserva a ordenação de uma sequência produzida por operadores que impõem ordens para o resto da consulta. Por outras palavras, operadores como OrderBy e ThenBy são tratados como se fossem seguidos de uma chamada para AsOrdered.

Operadores de consulta e pedidos

Os operadores de consulta a seguir introduzem a preservação de ordem em todas as operações subsequentes em uma consulta, ou até AsUnordered que 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 sua 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 não está ordenada
Aggregate Produção não determinística para operações não associativas ou não comutativas Produção 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 Produção não determinística para operações não associativas ou não comutativas Produção 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 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 não deterministicamente em paralelo Executa não deterministicamente 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 secção encomendada
OrderByDescending Reordena a sequência Inicia nova secção encomendada
Range Não aplicável (mesmo padrão que AsParallel ) Não aplicável
Repeat Não aplicável (mesmo padrão que AsParallel) Não aplicável
Reverse Reversos 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ística. Executa SkipWhile na ordem arbitrária atual
Sum Produção não determinística para operações não associativas ou não comutativas Produção não determinística para operações não associativas ou não comutativas
Take Leva os primeiros n elementos Leva todos os n elementos
TakeWhile Resultados ordenados Não determinística. 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 nenhuma lógica de ordenação especial aplicada a eles. Em alguns casos, uma consulta não ordenada pode manter a ordem da sequência de origem. Para consultas que usam o operador Select indexado, o PLINQ garante que os elementos de saída sairão na ordem crescente dos índices, mas não garante quais índices serão atribuídos a quais elementos.

Consulte também