Niestandardowe partycjonery dla PLINQ i TPLCustom Partitioners for PLINQ and TPL

Aby zrównoleglanie operację do źródła danych, jednym z najważniejszych kroków jest partycjonowanie źródła w wielu sekcjach, do których można uzyskać dostęp jednocześnie przez wiele wątków.To parallelize an operation on a data source, one of the essential steps is to partition the source into multiple sections that can be accessed concurrently by multiple threads. PLINQ i Biblioteka zadań równoległych (TPL) zapewniają domyślne partycje, które działają w sposób przezroczysty podczas pisania kwerendy równoległej lub pętli ForEach.PLINQ and the Task Parallel Library (TPL) provide default partitioners that work transparently when you write a parallel query or ForEach loop. W przypadku bardziej zaawansowanych scenariuszy można podłączyć własną partycję.For more advanced scenarios, you can plug in your own partitioner.

Rodzaje partycjonowaniaKinds of Partitioning

Istnieje wiele sposobów partycjonowania źródła danych.There are many ways to partition a data source. W najbardziej wydajnych podejściach wiele wątków współdziała w celu przetworzenia oryginalnej sekwencji źródłowej, a nie fizycznego oddzielenia źródła na wiele podsekwencji.In the most efficient approaches, multiple threads cooperate to process the original source sequence, rather than physically separating the source into multiple subsequences. W przypadku tablic i innych indeksowanych źródeł, takich jak kolekcje IList, w których długość jest znana z wyprzedzeniem, partycjonowanie zakresu jest Najprostszym rodzajem partycjonowania.For arrays and other indexed sources such as IList collections where the length is known in advance, range partitioning is the simplest kind of partitioning. Każdy wątek otrzymuje unikatowy indeks początkowy i końcowy, dzięki czemu może przetwarzać swój zakres źródła bez zastępowania lub przesłonięcia przez inny wątek.Every thread receives unique beginning and ending indexes, so that it can process its range of the source without overwriting or being overwritten by any other thread. Jedyne obciążenie związane z partycjonowaniem zakresu jest początkową pracą tworzenia zakresów; po tym zakończeniu nie jest wymagana żadna dodatkowa synchronizacja.The only overhead involved in range partitioning is the initial work of creating the ranges; no additional synchronization is required after that. W związku z tym może zapewnić dobrą wydajność, o ile obciążenie jest podzielone równomiernie.Therefore, it can provide good performance as long as the workload is divided evenly. Wadą partycjonowania zakresu jest to, że jeśli jeden wątek kończy się wczesnie, nie może pomóc innym wątkom zakończyć pracę.A disadvantage of range partitioning is that if one thread finishes early, it cannot help the other threads finish their work.

W przypadku list połączonych lub innych kolekcji, których długość nie jest znana, można użyć partycjonowania fragmentów.For linked lists or other collections whose length is not known, you can use chunk partitioning. W przypadku partycjonowania fragmentów każdy wątek lub zadanie w pętli równoległej lub zapytaniu zużywa pewną liczbę elementów źródłowych w jednym fragmencie, przetwarza je, a następnie wraca do pobrania dodatkowych elementów.In chunk partitioning, every thread or task in a parallel loop or query consumes some number of source elements in one chunk, processes them, and then comes back to retrieve additional elements. Partycja gwarantuje, że wszystkie elementy są dystrybuowane i że nie ma duplikatów.The partitioner ensures that all elements are distributed and that there are no duplicates. Fragment może być dowolnym rozmiarem.A chunk may be any size. Na przykład partycja, która jest przedstawiona w instrukcje: implementowanie partycji dynamicznych tworzy fragmenty, które zawierają tylko jeden element.For example, the partitioner that is demonstrated in How to: Implement Dynamic Partitions creates chunks that contain just one element. Tak długo, jak fragmenty nie są zbyt duże, ten rodzaj partycjonowania jest założenia równoważenia obciążenia, ponieważ przypisanie elementów do wątków nie jest wstępnie ustalone.As long as the chunks are not too large, this kind of partitioning is inherently load-balancing because the assignment of elements to threads is not pre-determined. Jednak program Partitioner ponosi obciążenie związane z synchronizacją za każdym razem, gdy wątek musi uzyskać kolejny fragment.However, the partitioner does incur the synchronization overhead each time the thread needs to get another chunk. Ilość synchronizacji ponoszonych w tych przypadkach jest odwrotnie proporcjonalna do rozmiaru fragmentów.The amount of synchronization incurred in these cases is inversely proportional to the size of the chunks.

Ogólnie partycjonowanie zakresu jest szybsze, gdy czas wykonywania delegata jest mały do umiarkowany, a źródło ma dużą liczbę elementów, a całkowita praca każdej partycji jest w przybliżeniu równoważna.In general, range partitioning is only faster when the execution time of the delegate is small to moderate, and the source has a large number of elements, and the total work of each partition is roughly equivalent. Partycjonowanie fragmentów jest zatem ogólnie szybsze w większości przypadków.Chunk partitioning is therefore generally faster in most cases. W przypadku źródeł o niewielkiej liczbie elementów lub dłuższym czasie wykonywania dla delegata, wydajność partycjonowania fragmentu i zakresu jest równa.On sources with a small number of elements or longer execution times for the delegate, then the performance of chunk and range partitioning is about equal.

Partycje TPL obsługują również dynamiczną liczbę partycji.The TPL partitioners also support a dynamic number of partitions. Oznacza to, że mogą tworzyć partycje na bieżąco, na przykład gdy pętla ForEach duplikuje nowe zadanie.This means they can create partitions on-the-fly, for example, when the ForEach loop spawns a new task. Ta funkcja umożliwia skalowanie ze sobą przy użyciu samej pętli.This feature enables the partitioner to scale together with the loop itself. Dynamiczne partycje są również z równoważeniem obciążenia.Dynamic partitioners are also inherently load-balancing. Podczas tworzenia niestandardowej partycji należy obsługiwać partycjonowanie dynamiczne, aby można było go używać z pętli ForEach.When you create a custom partitioner, you must support dynamic partitioning to be consumable from a ForEach loop.

Konfigurowanie partycji równoważenia obciążenia dla PLINQConfiguring Load Balancing Partitioners for PLINQ

Niektóre przeciążenia metody Partitioner.Create umożliwiają utworzenie partycji dla tablicy lub źródła IList i określenie, czy należy podjąć próbę zrównoważenia obciążenia między wątki.Some overloads of the Partitioner.Create method let you create a partitioner for an array or IList source and specify whether it should attempt to balance the workload among the threads. Gdy partycja jest skonfigurowana do równoważenia obciążenia, używane jest partycjonowanie fragmentu, a elementy są przekazywane do każdej partycji w małych fragmentach w miarę ich żądania.When the partitioner is configured to load-balance, chunk partitioning is used, and the elements are handed off to each partition in small chunks as they are requested. Takie podejście pozwala upewnić się, że wszystkie partycje mają elementy do przetworzenia, dopóki cała pętla lub kwerenda nie zostanie ukończona.This approach helps ensure that all partitions have elements to process until the entire loop or query is completed. Dodatkowe Przeciążenie można wykorzystać w celu zapewnienia partycjonowania w ramach równoważenia obciążenia dowolnego źródła IEnumerable.An additional overload can be used to provide load-balancing partitioning of any IEnumerable source.

Ogólnie rzecz biorąc, równoważenie obciążenia wymaga, aby partycje często żądały elementów z partycji.In general, load balancing requires the partitions to request elements relatively frequently from the partitioner. Z kolei partycja, która umożliwia partycjonowanie statycznej, może przypisywać elementy do każdego programu Partitioner wszystkie jednocześnie, używając partycjonowania zakresu lub fragmentu.By contrast, a partitioner that does static partitioning can assign the elements to each partitioner all at once by using either range or chunk partitioning. Wymaga to mniejszego obciążenia niż Równoważenie obciążenia, ale może trwać dłużej, jeśli jeden wątek zostanie zakończony znacznie większym nakładem pracy niż inne.This requires less overhead than load balancing, but it might take longer to execute if one thread ends up with significantly more work than the others. Domyślnie, gdy przeszedł element IList lub Array, PLINQ zawsze używa partycjonowania zakresu bez równoważenia obciążenia.By default when it is passed an IList or an array, PLINQ always uses range partitioning without load balancing. Aby włączyć równoważenie obciążenia dla PLINQ, użyj metody Partitioner.Create, jak pokazano w poniższym przykładzie.To enable load balancing for PLINQ, use the Partitioner.Create method, as shown in the following example.

// Static partitioning requires indexable source. Load balancing
// can use any IEnumerable.
var nums = Enumerable.Range(0, 100000000).ToArray();

// Create a load-balancing partitioner. Or specify false for static partitioning.
Partitioner<int> customPartitioner = Partitioner.Create(nums, true);

// The partitioner is the query's data source.
var q = from x in customPartitioner.AsParallel()
        select x * Math.PI;

q.ForAll((x) =>
{
    ProcessData(x);
});
' Static number of partitions requires indexable source.
Dim nums = Enumerable.Range(0, 100000000).ToArray()

' Create a load-balancing partitioner. Or specify false For  Shared partitioning.
Dim customPartitioner = Partitioner.Create(nums, True)

' The partitioner is the query's data source.
Dim q = From x In customPartitioner.AsParallel()
        Select x * Math.PI

q.ForAll(Sub(x) ProcessData(x))

Najlepszym sposobem, aby określić, czy należy używać równoważenia obciążenia w danym scenariuszu, jest eksperymentowanie i pomiar, jak długo trwa wykonywanie operacji w ramach reprezentatywnych obciążeń i konfiguracji komputerów.The best way to determine whether to use load balancing in any given scenario is to experiment and measure how long it takes operations to complete under representative loads and computer configurations. Na przykład partycjonowanie statyczne może zapewnić znaczący przyspieszenie na komputerze z wieloma rdzeniami, który ma tylko kilka rdzeni, ale może to spowodować spowolnienie na komputerach mających stosunkowo wiele rdzeni.For example, static partitioning might provide significant speedup on a multi-core computer that has only a few cores, but it might result in slowdowns on computers that have relatively many cores.

Poniższa tabela zawiera listę dostępnych przeciążeń metody Create.The following table lists the available overloads of the Create method. Te partycje nie są ograniczone do użycia tylko z PLINQ lub Task.These partitioners are not limited to use only with PLINQ or Task. Mogą być również używane w przypadku dowolnej niestandardowej konstrukcji równoległej.They can also be used with any custom parallel construct.

WystępująOverload Używa równoważenia obciążeniaUses load balancing
Create<TSource>(IEnumerable<TSource>) stałegoAlways
Create<TSource>(TSource[], Boolean) Gdy argument logiczny jest określony jako trueWhen the Boolean argument is specified as true
Create<TSource>(IList<TSource>, Boolean) Gdy argument logiczny jest określony jako trueWhen the Boolean argument is specified as true
Create(Int32, Int32) UstawioneNever
Create(Int32, Int32, Int32) UstawioneNever
Create(Int64, Int64) UstawioneNever
Create(Int64, Int64, Int64) UstawioneNever

Konfigurowanie partycji zakresów statycznych dla elementu Parallel. ForEachConfiguring Static Range Partitioners for Parallel.ForEach

W pętli For treść pętli jest udostępniana metodzie jako delegat.In a For loop, the body of the loop is provided to the method as a delegate. Koszt wywołania tego delegata jest taki sam jak wywołanie metody wirtualnej.The cost of invoking that delegate is about the same as a virtual method call. W niektórych scenariuszach treść pętli równoległej może być wystarczająco mała, że koszt wywołania delegata w każdej iteracji zostanie znaczący.In some scenarios, the body of a parallel loop might be small enough that the cost of the delegate invocation on each loop iteration becomes significant. W takich sytuacjach można użyć jednego z przeciążeń Create, aby utworzyć IEnumerable<T> partycji zakresu względem elementów źródłowych.In such situations, you can use one of the Create overloads to create an IEnumerable<T> of range partitions over the source elements. Następnie można przekazać tę kolekcję zakresów do metody ForEach, której treść składa się z zwykłej pętli forowej.Then, you can pass this collection of ranges to a ForEach method whose body consists of a regular for loop. Zaletą tego podejścia jest to, że koszt wywołania delegata jest naliczany tylko raz dla każdego zakresu, a nie raz na element.The benefit of this approach is that the delegate invocation cost is incurred only once per range, rather than once per element. Poniższy przykład demonstruje wzorzec podstawowy.The following example demonstrates the basic pattern.

using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {

        // Source must be array or IList.
        var source = Enumerable.Range(0, 100000).ToArray();

        // Partition the entire source array.
        var rangePartitioner = Partitioner.Create(0, source.Length);

        double[] results = new double[source.Length];

        // Loop over the partitions in parallel.
        Parallel.ForEach(rangePartitioner, (range, loopState) =>
        {
            // Loop over each range element without a delegate invocation.
            for (int i = range.Item1; i < range.Item2; i++)
            {
                results[i] = source[i] * Math.PI;
            }
        });

        Console.WriteLine("Operation complete. Print results? y/n");
        char input = Console.ReadKey().KeyChar;
        if (input == 'y' || input == 'Y')
        {
            foreach(double d in results)
            {
                Console.Write("{0} ", d);
            }           
        }
    }
}
Imports System.Threading.Tasks
Imports System.Collections.Concurrent

Module PartitionDemo

    Sub Main()
        ' Source must be array or IList.
        Dim source = Enumerable.Range(0, 100000).ToArray()

        ' Partition the entire source array. 
        ' Let the partitioner size the ranges.
        Dim rangePartitioner = Partitioner.Create(0, source.Length)

        Dim results(source.Length - 1) As Double

        ' Loop over the partitions in parallel. The Sub is invoked
        ' once per partition.
        Parallel.ForEach(rangePartitioner, Sub(range, loopState)

                                               ' Loop over each range element without a delegate invocation.
                                               For i As Integer = range.Item1 To range.Item2 - 1
                                                   results(i) = source(i) * Math.PI
                                               Next
                                           End Sub)
        Console.WriteLine("Operation complete. Print results? y/n")
        Dim input As Char = Console.ReadKey().KeyChar
        If input = "y"c Or input = "Y"c Then
            For Each d As Double In results
                Console.Write("{0} ", d)
            Next
        End If

    End Sub
End Module

Każdy wątek w pętli otrzymuje własne Tuple<T1,T2>, które zawierają początkową i końcową wartość indeksu w określonym zakresie.Every thread in the loop receives its own Tuple<T1,T2> that contains the starting and ending index values in the specified sub-range. Wewnętrzna pętla for używa wartości fromInclusive i toExclusive do bezpośredniej pętli tablicy lub IList.The inner for loop uses the fromInclusive and toExclusive values to loop over the array or the IList directly.

Jeden z przeciążeń Create umożliwia określenie rozmiaru partycji i liczby partycji.One of the Create overloads lets you specify the size of the partitions, and the number of partitions. Tego przeciążenia można używać w scenariuszach, w których prace na element są tak niskie, że nawet jedno wywołanie metody wirtualnej na element ma zauważalny wpływ na wydajność.This overload can be used in scenarios where the work per element is so low that even one virtual method call per element has a noticeable impact on performance.

Niestandardowe partycjeCustom Partitioners

W niektórych scenariuszach może być wartościowa, a nawet wymagane do zaimplementowania własnego programu Partitioner.In some scenarios, it might be worthwhile or even required to implement your own partitioner. Na przykład może istnieć niestandardowa Klasa kolekcji, która może być bardziej wydajna niż domyślna partycja, na podstawie wiedzy o wewnętrznej strukturze klasy.For example, you might have a custom collection class that you can partition more efficiently than the default partitioners can, based on your knowledge of the internal structure of the class. Możesz również utworzyć partycje zakresu o różnych rozmiarach na podstawie wiedzy o tym, jak długo będzie przetwarzać elementy w różnych lokalizacjach w kolekcji źródłowej.Or, you may want to create range partitions of varying sizes based on your knowledge of how long it will take to process elements at different locations in the source collection.

Aby utworzyć podstawową partycję niestandardową, Utwórz klasę z System.Collections.Concurrent.Partitioner<TSource> i Zastąp metody wirtualne zgodnie z opisem w poniższej tabeli.To create a basic custom partitioner, derive a class from System.Collections.Concurrent.Partitioner<TSource> and override the virtual methods, as described in the following table.

GetPartitions Ta metoda jest wywoływana raz przez wątek główny i zwraca IList (IEnumerator (TSource)).This method is called once by the main thread and returns an IList(IEnumerator(TSource)). Każdy wątek roboczy w pętli lub zapytaniu może wywoływać GetEnumerator na liście, aby pobrać IEnumerator<T> na oddzielnej partycji.Each worker thread in the loop or query can call GetEnumerator on the list to retrieve a IEnumerator<T> over a distinct partition.
SupportsDynamicPartitions Zwróć true w przypadku zaimplementowania GetDynamicPartitions, w przeciwnym razie, false.Return true if you implement GetDynamicPartitions, otherwise, false.
GetDynamicPartitions Jeśli SupportsDynamicPartitions jest true, ta metoda może być wywoływana zamiast GetPartitions.If SupportsDynamicPartitions is true, this method can optionally be called instead of GetPartitions.

Jeśli wyniki muszą być sortowane lub wymagają dostępu indeksowanego do elementów, utwórz je od System.Collections.Concurrent.OrderablePartitioner<TSource> i Zastąp metody wirtualne zgodnie z opisem w poniższej tabeli.If the results must be sortable or you require indexed access into the elements, then derive from System.Collections.Concurrent.OrderablePartitioner<TSource> and override its virtual methods as described in the following table.

GetPartitions Ta metoda jest wywoływana raz przez wątek główny i zwraca IList(IEnumerator(TSource)).This method is called once by the main thread and returns an IList(IEnumerator(TSource)). Każdy wątek roboczy w pętli lub zapytaniu może wywoływać GetEnumerator na liście, aby pobrać IEnumerator<T> na oddzielnej partycji.Each worker thread in the loop or query can call GetEnumerator on the list to retrieve a IEnumerator<T> over a distinct partition.
SupportsDynamicPartitions Zwróć true w przypadku zaimplementowania GetDynamicPartitions; w przeciwnym razie false.Return true if you implement GetDynamicPartitions; otherwise, false.
GetDynamicPartitions Zazwyczaj to właśnie wywołuje GetOrderableDynamicPartitions.Typically, this just calls GetOrderableDynamicPartitions.
GetOrderableDynamicPartitions Jeśli SupportsDynamicPartitions jest true, ta metoda może być wywoływana zamiast GetPartitions.If SupportsDynamicPartitions is true, this method can optionally be called instead of GetPartitions.

W poniższej tabeli znajdują się dodatkowe szczegółowe informacje o tym, jak trzy rodzaje partycji równoważenia obciążenia implementują klasę OrderablePartitioner<TSource>.The following table provides additional details about how the three kinds of load-balancing partitioners implement the OrderablePartitioner<TSource> class.

Metoda/WłaściwośćMethod/Property Elementy IList/Array bez równoważenia obciążeniaIList / Array without Load Balancing Elementy IList/Array z równoważeniem obciążeniaIList / Array with Load Balancing IEnumerableIEnumerable
GetOrderablePartitions Używa partycjonowania zakresuUses range partitioning Używa partycjonowania fragmentów zoptymalizowanego dla list dla partitionCount określonychUses chunk partitioning optimized for Lists for the partitionCount specified Używa partycjonowania fragmentów przez utworzenie statycznej liczby partycji.Uses chunk partitioning by creating a static number of partitions.
OrderablePartitioner<TSource>.GetOrderableDynamicPartitions Zgłasza wyjątek nieobsługiwanyThrows not-supported exception Używa partycjonowania fragmentów zoptymalizowanego pod kątem list i partycji dynamicznychUses chunk partitioning optimized for Lists and dynamic partitions Używa partycjonowania fragmentów przez utworzenie dynamicznej liczby partycji.Uses chunk partitioning by creating a dynamic number of partitions.
KeysOrderedInEachPartition Zwraca trueReturns true Zwraca trueReturns true Zwraca trueReturns true
KeysOrderedAcrossPartitions Zwraca trueReturns true Zwraca falseReturns false Zwraca falseReturns false
KeysNormalized Zwraca trueReturns true Zwraca trueReturns true Zwraca trueReturns true
SupportsDynamicPartitions Zwraca falseReturns false Zwraca trueReturns true Zwraca trueReturns true

Partycje dynamiczneDynamic Partitions

Jeśli zamierzasz używać partycji w metodzie ForEach, musisz mieć możliwość zwrócenia dynamicznej liczby partycji.If you intend the partitioner to be used in a ForEach method, you must be able to return a dynamic number of partitions. Oznacza to, że partycja może dostarczyć moduł wyliczający dla nowej partycji na żądanie w dowolnym momencie podczas wykonywania pętli.This means that the partitioner can supply an enumerator for a new partition on-demand at any time during loop execution. W zasadzie Każda pętla dodaje nowe zadanie równoległe, żąda nowej partycji dla tego zadania.Basically, whenever the loop adds a new parallel task, it requests a new partition for that task. Jeśli wymaga się, aby dane były możliwe do uporządkowania, należy utworzyć je od System.Collections.Concurrent.OrderablePartitioner<TSource> tak, aby każdy element w każdej partycji miał przypisany unikatowy indeks.If you require the data to be orderable, then derive from System.Collections.Concurrent.OrderablePartitioner<TSource> so that each item in each partition is assigned a unique index.

Aby uzyskać więcej informacji, zobacz temat jak: implementowanie partycji dynamicznych.For more information, and an example, see How to: Implement Dynamic Partitions.

Kontrakt dla partycjiContract for Partitioners

Podczas implementowania niestandardowego programu Partitioner postępuj zgodnie z poniższymi wskazówkami, aby zapewnić poprawną interakcję z PLINQ i ForEach w TPL:When you implement a custom partitioner, follow these guidelines to help ensure correct interaction with PLINQ and ForEach in the TPL:

  • Jeśli GetPartitions jest wywoływana z argumentem równym zero lub mniejszym dla partitionsCount, throw ArgumentOutOfRangeException.If GetPartitions is called with an argument of zero or less for partitionsCount, throw ArgumentOutOfRangeException. Mimo że PLINQ i TPL nigdy nie przekażą do partitionCount równej 0, jednak zalecamy ochronę przed możliwością.Although PLINQ and TPL will never pass in a partitionCount equal to 0, we nevertheless recommend that you guard against the possibility.

  • GetPartitions i GetOrderablePartitions powinny zawsze zwracać partitionsCount liczbę partycji.GetPartitions and GetOrderablePartitions should always return partitionsCount number of partitions. Jeśli program partitioner nie może utworzyć tylu partycji zgodnie z żądaniem, metoda powinna zwrócić pusty moduł wyliczający dla każdej z pozostałych partycji.If the partitioner runs out of data and cannot create as many partitions as requested, then the method should return an empty enumerator for each of the remaining partitions. W przeciwnym razie zarówno PLINQ, jak i TPL będą zgłaszać InvalidOperationException.Otherwise, both PLINQ and TPL will throw an InvalidOperationException.

  • GetPartitions, GetOrderablePartitions, GetDynamicPartitionsi GetOrderableDynamicPartitions nigdy nie powinny zwracać null (Nothing w Visual Basic).GetPartitions, GetOrderablePartitions, GetDynamicPartitions, and GetOrderableDynamicPartitions should never return null (Nothing in Visual Basic). W takim przypadku PLINQ/TPL zgłosi InvalidOperationException.If they do, PLINQ / TPL will throw an InvalidOperationException.

  • Metody, które zwracają partycje, powinny zawsze zwracać partycje, które mogą w pełni i jednoznacznie wyliczyć źródło danych.Methods that return partitions should always return partitions that can fully and uniquely enumerate the data source. Nie powinno być duplikowane w źródle danych ani elementy pominięte, chyba że jest to wymagane przez projekt partycji.There should be no duplication in the data source or skipped items unless specifically required by the design of the partitioner. Jeśli ta reguła nie zostanie zastosowana, kolejność danych wyjściowych może zostać zaszyfrowana.If this rule is not followed, then the output order may be scrambled.

  • Następujące metody pobierające dane logiczne muszą zawsze prawidłowo zwracać następujące wartości, aby kolejność wyjściowa nie została zaszyfrowana:The following Boolean getters must always accurately return the following values so that the output order is not scrambled:

    • KeysOrderedInEachPartition: Każda partycja zwraca elementy z rosnącymi indeksami kluczy.KeysOrderedInEachPartition: Each partition returns elements with increasing key indices.

    • KeysOrderedAcrossPartitions: dla wszystkich zwracanych partycji, indeksy kluczy na partycji i są wyższe niż indeksy kluczy na partycji i-1.KeysOrderedAcrossPartitions: For all partitions that are returned, the key indices in partition i are higher than the key indices in partition i-1.

    • KeysNormalized: wszystkie indeksy kluczowych monotonicznie zwiększają się bez przerw, zaczynając od zera.KeysNormalized: All key indices are monotonically increasing without gaps, starting from zero.

  • Wszystkie indeksy muszą być unikatowe.All indices must be unique. Nie można duplikować indeksów.There may not be duplicate indices. Jeśli ta reguła nie zostanie zastosowana, kolejność danych wyjściowych może zostać zaszyfrowana.If this rule is not followed, then the output order may be scrambled.

  • Wszystkie indeksy muszą być nieujemne.All indices must be nonnegative. Jeśli ta reguła nie zostanie zastosowana, PLINQ/TPL może zgłosić wyjątki.If this rule is not followed, then PLINQ/TPL may throw exceptions.

Zobacz takżeSee also