Conservar el orden en PLINQ

En PLINQ, el objetivo es maximizar el rendimiento manteniendo la exactitud. Una consulta se debería ejecutar lo más rápido que fuese posible pero con resultados correctos. La exactitud exige que se conserve el orden de la secuencia de origen en algunos casos; sin embargo, la ordenación puede suponer la utilización de muchos recursos de computación. Por consiguiente, de forma predeterminada, PLINQ no conserva el orden de la secuencia de origen. En este sentido, PLINQ se parece a LINQ to SQL, pero es diferente de LINQ to Objects, que conserva el orden.

Para reemplazar el comportamiento predeterminado, puede activar la capacidad de conservar el orden utilizando el operador AsOrdered en la secuencia de origen. Después, puede desactivarla en la consulta, utilizando el método AsUnordered. Con ambos métodos, se procesa la consulta basándose en la heurística que determina si la consulta se debe ejecutar de forma paralela o secuencial. Para más información, consulte Introducción a la velocidad en PLINQ.

En el siguiente ejemplo se muestra una consulta paralela no ordenada que filtra todos los elementos que coinciden con una condición, sin intentar ordenar los resultados de forma alguna.

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 no obtiene necesariamente las 1000 primeras ciudades de la secuencia de origen que cumplen la condición, sino que algún conjunto de las 1000 ciudades que la cumplen. Los operadores de consulta PLINQ particionan la secuencia de origen en varias secuencias secundarias que se procesan como tareas simultáneas. Si no se especifica que se conserve el orden, los resultados de cada partición se presentan a la siguiente etapa de la consulta con un orden arbitrario. Por otra parte, una partición puede producir un subconjunto de los resultados antes de continuar procesando los elementos restantes. El orden resultante puede ser diferente cada vez. Una aplicación no puede controlar este hecho, porque depende de cómo programe los subprocesos el sistema operativo.

En el siguiente ejemplo se reemplaza el comportamiento predeterminado utilizando al operador AsOrdered en la secuencia de origen. De esta forma se garantiza que el método Take devuelve las 1000 primeras ciudades de la secuencia de origen que cumplen la condición.

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)

Sin embargo, esta consulta probablemente no se ejecute tan rápido como la versión no ordenada, porque debe realizar el seguimiento del orden original en todas las particiones y, en el momento de la fusión mediante combinación, garantizar que el orden es coherente. Por consiguiente, recomendamos usar AsOrdered solo cuando sea estrictamente necesario y únicamente para las partes de la consulta que lo requieran. Cuando ya no sea necesario conservar el orden, use AsUnordered para desactivarlo. En el siguiente ejemplo se consigue mediante la creación de dos 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 conserva el orden de una secuencia generada por operadores que imponen el orden para el resto de la consulta. En otras palabras, los operadores de tipo OrderBy y ThenBy se tratan como si fuesen seguidos de una llamada a AsOrdered.

Operadores de consulta y ordenación

Los siguientes operadores de consulta introducen la conservación del orden en todas las operaciones posteriores de una consulta o hasta que se llame a AsUnordered:

En algunos casos, los siguientes operadores de consulta PLINQ pueden requerir secuencias de origen ordenadas para generar resultados correctos:

Algunos operadores de consulta PLINQ se comportan de manera diferente, dependiendo de si su secuencia de origen está ordenada o no. En la siguiente tabla se enumeran estos operadores.

Operador Resultado cuando la secuencia de origen está ordenada Resultado cuando la secuencia de origen no está ordenada
Aggregate Salida no determinista para operaciones no asociativas o no conmutativas. Salida no determinista para operaciones no asociativas o no conmutativas.
All No aplicable No disponible
Any No disponible No disponible
AsEnumerable No disponible No aplicable
Average Salida no determinista para operaciones no asociativas o no conmutativas. Salida no determinista para operaciones no asociativas o no conmutativas.
Cast Resultados ordenados Resultados no ordenados
Concat Resultados ordenados Resultados no ordenados
Count No aplicable No disponible
DefaultIfEmpty No disponible No aplicable
Distinct Resultados ordenados Resultados no ordenados
ElementAt Se devuelve el elemento especificado Elemento arbitrario
ElementAtOrDefault Se devuelve el elemento especificado Elemento arbitrario
Except Resultados no ordenados Resultados no ordenados
First Se devuelve el elemento especificado Elemento arbitrario
FirstOrDefault Se devuelve el elemento especificado Elemento arbitrario
ForAll Ejecución no determinista en paralelo Ejecución no determinista en paralelo
GroupBy Resultados ordenados Resultados no ordenados
GroupJoin Resultados ordenados Resultados no ordenados
Intersect Resultados ordenados Resultados no ordenados
Join Resultados ordenados Resultados no ordenados
Last Se devuelve el elemento especificado Elemento arbitrario
LastOrDefault Se devuelve el elemento especificado Elemento arbitrario
LongCount No aplicable No disponible
Min No disponible No aplicable
OrderBy Reordena la secuencia Inicia una nueva sección ordenada
OrderByDescending Reordena la secuencia Inicia una nueva sección ordenada
Range No aplicable (el mismo valor predeterminado que AsParallel) No aplicable
Repeat No aplicable (el mismo valor predeterminado que AsParallel) No aplicable
Reverse Invierte el orden No hace nada
Select Resultados ordenados Resultados no ordenados
Select (indexada) Resultados ordenados Resultados no ordenados.
SelectMany Resultados ordenados. Resultados no ordenados
SelectMany (indexada) Resultados ordenados. Resultados no ordenados.
SequenceEqual Comparación ordenada Comparación no ordenada
Single No aplicable No disponible
SingleOrDefault No disponible No aplicable
Skip Omite los primeros n elementos Omite todos los n elementos
SkipWhile Resultados ordenados. No determinista. Ejecuta SkipWhile en el orden arbitrario actual
Sum Salida no determinista para operaciones no asociativas o no conmutativas. Salida no determinista para operaciones no asociativas o no conmutativas.
Take Toma los n primeros elementos Toma cualquier elemento n
TakeWhile Resultados ordenados No determinista. Ejecuta TakeWhile en el orden arbitrario actual
ThenBy Complementa OrderBy Complementa OrderBy
ThenByDescending Complementa OrderBy Complementa OrderBy
ToArray Resultados ordenados Resultados no ordenados
ToDictionary No aplicable No aplicable
ToList Resultados ordenados Resultados no ordenados
ToLookup Resultados ordenados Resultados no ordenados
Union Resultados ordenados Resultados no ordenados
Where Resultados ordenados Resultados no ordenados
Where (indexada) Resultados ordenados Resultados no ordenados
Zip Resultados ordenados Resultados no ordenados

Los resultados desordenados no se ordenan aleatoriamente de forma activa; simplemente no se les aplica ninguna lógica de ordenación especial. En algunos casos, una consulta desordenada puede conservar el orden de la secuencia de origen. En las consultas que usan el operador Select indizado, PLINQ garantiza que los elementos de salida aparecerán en el orden de los índices ascendentes, pero no ofrece ninguna garantía sobre qué índices se asignarán a qué elementos.

Vea también