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.