Conservation de l'ordre en PLINQ

Dans PLINQ, l’objectif est d’augmenter les performances tout en préservant l’exactitude. Une requête doit s’exécuter aussi rapidement que possible, mais toujours générer des résultats corrects. Dans certains cas, l’exactitude requiert que l’ordre de la séquence source soit conservé ; toutefois, le classement peut coûter cher en calcul. Par conséquent, par défaut, PLINQ ne conserve pas l’ordre de la séquence source. À cet égard, PLINQ ressemble à LINQ to SQL, mais ne ressemble pas à LINQ to Objects, qui préserve l'ordre.

Pour remplacer le comportement par défaut, vous pouvez activer la conservation de l’ordre à l’aide de l’opérateur AsOrdered sur la séquence source. Vous pouvez désactiver la préservation de l’ordre plus loin dans la requête à l’aide de la méthode AsUnordered. Ces deux méthodes permettent de traiter la requête en fonction des paramètres heuristiques, qui déterminent s’il faut exécuter la requête en parallèle ou de manière séquentielle. Pour plus d’informations, consultez Fonctionnement de l’accélération dans PLINQ.

L’exemple suivant montre une requête parallèle non classée qui filtre tous les éléments qui correspondent à une condition, sans essayer de classer les résultats de quelque manière que ce soit.

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)

Cette requête ne produit pas nécessairement les 1 000 premières villes de la séquence source qui remplissent la condition, mais plutôt un ensemble de 1 000 villes qui remplissent la condition. Les opérateurs de requête PLINQ partitionnent la séquence source en plusieurs sous-séquences qui sont traitées comme des tâches simultanées. Si la conservation de l’ordre n’est pas spécifiée, les résultats de chaque partition sont remis à l’étape suivante de la requête dans un ordre arbitraire. Par ailleurs, une partition peut donner un sous-ensemble de ses résultats avant de continuer à traiter les éléments restants. L’ordre résultant peut être différent à chaque fois. Ceci ne peut pas être contrôlé par votre application, car cela dépend de la manière dont le système d’exploitation planifie les threads.

L’exemple suivant remplace le comportement par défaut à l’aide de l’opérateur AsOrdered sur la séquence source. Cela garantit que la méthode Take retourne les 1 000 premières villes de la séquence source qui remplissent la condition.

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)

Toutefois, cette requête ne s’exécute probablement pas aussi rapidement que la version non classée, car elle doit surveiller l’ordre d’origine dans toutes les partitions et s’assurer que le classement est cohérent au moment de la fusion. Par conséquent, nous vous recommandons de n’utiliser AsOrdered que si c’est nécessaire, et uniquement pour les parties de la requête qui l’exigent. Lorsque la conservation de l’ordre n’est plus nécessaire, utilisez AsUnordered pour la désactiver. L’exemple suivant obtient ce résultat en composant deux requêtes.

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

Notez que PLINQ conserve le classement d’une séquence produite par des opérateurs imposant un ordre pour le reste de la requête. En d’autres termes, les opérateurs tels que OrderBy et ThenBy sont traités comme s’ils étaient suivis d’un appel à AsOrdered.

Opérateurs de requête et de classement

Les opérateurs de requête suivants présentent la conservation de l’ordre dans toutes les opérations suivantes d’une requête, ou jusqu'à ce que AsUnordered soit appelée :

dans certains cas, les opérateurs de requête PLINQ suivants peuvent exiger des séquences source classées pour produire des résultats corrects :

certains opérateurs de requête PLINQ se comportent différemment, selon que leur séquence source est classée ou non classée. Le tableau suivant répertorie ces opérateurs.

Opérateur Résultat lorsque la séquence source est classée Résultat de la séquence source n’est pas classée
Aggregate Sortie non déterministe pour les opérations non associatives ou non commutatives Sortie non déterministe pour les opérations non associatives ou non commutatives
All Non applicable Non applicable
Any Non applicable Non applicable
AsEnumerable Non applicable Non applicable
Average Sortie non déterministe pour les opérations non associatives ou non commutatives Sortie non déterministe pour les opérations non associatives ou non commutatives
Cast Résultats classés Résultats non classés
Concat Résultats classés Résultats non classés
Count Non applicable Non applicable
DefaultIfEmpty Non applicable Non applicable
Distinct Résultats classés Résultats non classés
ElementAt Retourner un élément spécifié Élément arbitraire
ElementAtOrDefault Retourner un élément spécifié Élément arbitraire
Except Résultats non classés Résultats non classés
First Retourner un élément spécifié Élément arbitraire
FirstOrDefault Retourner un élément spécifié Élément arbitraire
ForAll Exécute de façon non déterministe en parallèle Exécute de façon non déterministe en parallèle
GroupBy Résultats classés Résultats non classés
GroupJoin Résultats classés Résultats non classés
Intersect Résultats classés Résultats non classés
Join Résultats classés Résultats non classés
Last Retourner un élément spécifié Élément arbitraire
LastOrDefault Retourner un élément spécifié Élément arbitraire
LongCount Non applicable Non applicable
Min Non applicable Non applicable
OrderBy Réorganise la séquence Démarre une nouvelle section classée
OrderByDescending Réorganise la séquence Démarre une nouvelle section classée
Range Non applicable (même valeur par défaut que AsParallel) Non applicable
Repeat Non applicable (même valeur par défaut que AsParallel) Non applicable
Reverse Inverse Sans effet
Select Résultats classés Résultats non classés
Select (indexé) Résultats classés Résultats non classés.
SelectMany Résultats classés. Résultats non classés
SelectMany (indexé) Résultats classés. Résultats non classés.
SequenceEqual Comparaison classée Comparaison non classée
Single Non applicable Non applicable
SingleOrDefault Non applicable Non applicable
Skip Ignore les n premiers éléments Ignore les n éléments
SkipWhile Résultats classés. Non déterministe. Exécute SkipWhile dans l’ordre arbitraire actuel
Sum Sortie non déterministe pour les opérations non associatives ou non commutatives Sortie non déterministe pour les opérations non associatives ou non commutatives
Take Prend les premiers éléments n Prend tous les éléments n
TakeWhile Résultats classés Non déterministe. Exécute TakeWhile dans l’ordre arbitraire actuel
ThenBy Complète OrderBy Complète OrderBy
ThenByDescending Complète OrderBy Complète OrderBy
ToArray Résultats classés Résultats non classés
ToDictionary Non applicable Non applicable
ToList Résultats classés Résultats non classés
ToLookup Résultats classés Résultats non classés
Union Résultats classés Résultats non classés
Where Résultats classés Résultats non classés
Where (indexé) Résultats classés Résultats non classés
Zip Résultats classés Résultats non classés

Les résultats non classés ne sont pas mélangés de manière active ; aucune logique de classement spéciale ne leur est appliquée. Dans certains cas, une requête non classée peut conserver le classement de la séquence source. Pour les requêtes utilisant l’opérateur Select indexé, PLINQ garantit que les éléments de sortie arriveront dans l’ordre des indices croissant, mais n’offre aucune garantie quant aux index qui seront affectés aux éléments respectifs.

Voir aussi