Compartir a través de


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<TSource>. 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 obtener más información, vea 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.

Dim cityQuery = From city In cities.AsParallel()
               Where City.Population > 10000
               Take (1000)
var cityQuery = (from city in cities.AsParallel()
                 where city.Population > 10000
                 select city)
                   .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<TSource> devuelve las 10 primeras ciudades de la secuencia de origen que cumplen la condición.

Dim orderedCities = From city In cities.AsParallel().AsOrdered()
                    Where City.Population > 10000
                    Take (1000)
            var orderedCities = (from city in cities.AsParallel().AsOrdered()
                                 where city.Population > 10000
                                 select city)
                                .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 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<TSource> para desactivarlo. En el siguiente ejemplo se consigue mediante la creación de dos consultas.

        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

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 { Name = city.Name, Pop = city.Population, Mayor = c.Mayor };

foreach (var city in finalResult) { /*...*/ }

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<TSource>:

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<TSource>

No es aplicable

No es aplicable

Any

No es aplicable

No es aplicable

AsEnumerable<TSource>

No es aplicable

No es aplicable

Average

Salida no determinista para operaciones no asociativas o no conmutativas.

Salida no determinista para operaciones no asociativas o no conmutativas.

Cast<TResult>

Resultados ordenados

Resultados no ordenados

Concat

Resultados ordenados

Resultados no ordenados

Count

No es aplicable

No es aplicable

DefaultIfEmpty

No es aplicable

No es aplicable

Distinct

Resultados ordenados

Resultados no ordenados

ElementAt<TSource>

Se devuelve el elemento especificado

Elemento arbitrario

ElementAtOrDefault<TSource>

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<TSource>

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 es aplicable

No es aplicable

Min

No es aplicable

No es 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 es aplicable

Repeat<TResult>

No aplicable (mismo valor predeterminado que AsParallel)

No es aplicable

Reverse<TSource>

Invierte el orden

No hace nada

Select

Resultados ordenados

Resultados no ordenados

Select (indizado)

Resultados ordenados

Resultados no ordenados.

SelectMany

Resultados ordenados.

Resultados no ordenados

SelectMany (indizado)

Resultados ordenados.

Resultados no ordenados.

SequenceEqual

Comparación ordenada

Comparación no ordenada

Single

No es aplicable

No es aplicable

SingleOrDefault

No es aplicable

No es aplicable

Skip<TSource>

Omite los n primeros elementos

Omite cualquier elemento n

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<TSource>

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

ToTSource[]

Resultados ordenados

Resultados no ordenados

ToDictionary

No es aplicable

No es aplicable

ToList<TSource>

Resultados ordenados

Resultados no ordenados

ToLookup

Resultados ordenados

Resultados no ordenados

Union

Resultados ordenados

Resultados no ordenados

Where

Resultados ordenados

Resultados no ordenados

Where (indizado)

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

Conceptos

Parallel LINQ (PLINQ)

Programación paralela en .NET Framework