PLINQ에서 순서 유지

PLINQ에서 목표는 정확성을 유지하면서 성능을 최대화하는 것입니다. 쿼리는 가능한 한 빠르게 실행되지만 올바른 결과를 생성해야 합니다. 경우에 따라 정확성을 위해 소스 시퀀스의 순서를 유지해야 하지만 순서 지정의 계산 비용이 높을 수 있습니다. 따라서 기본적으로 PLINQ는 소스 시퀀스의 순서를 유지하지 않습니다. 이러한 점에서 PLINQ는 LINQ to SQL과 유사하지만 순서를 유지하는 LINQ to Objects와는 다릅니다.

기본 동작을 재정의하려면 소스 시퀀스에서 AsOrdered 연산자를 사용하여 순서 유지 기능을 켤 수 있습니다. 그런 다음, AsUnordered 메서드를 사용하여 쿼리에서 나중에 순서 유지 기능을 끌 수 있습니다. 두 메서드를 모두 사용하면 쿼리를 병렬로 실행할지 또는 순차적으로 실행할지 여부를 결정하는 추론을 기반으로 쿼리가 처리됩니다. 자세한 내용은 PLINQ의 속도 향상 이해를 참조하세요.

다음 예제는 어떤 방식으로든 결과의 순서를 지정하지 않고 조건과 일치하는 모든 요소에 대해 필터링되는 순서가 지정되지 않은 병렬 쿼리를 보여줍니다.

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)

이 쿼리는 조건을 충족하는 처음 1000개 도시를 소스 시퀀스에서 생성하기보다는 조건을 충족하는 1000개 도시 집합을 생성합니다. PLINQ 쿼리 연산자는 소스 시퀀스를 동시 작업으로 처리되는 여러 하위 시퀀스로 분할합니다. 순서 유지를 지정하지 않으면 각 파티션의 결과가 쿼리의 다음 단계에 임의 순서로 전달됩니다. 또한 파티션이 나머지 요소를 계속 처리하기 전에 결과 하위 집합을 생성할 수 있습니다. 결과 순서는 매번 다를 수 있습니다. 순서는 운영 체제가 스레드를 예약하는 방법에 따라 다르므로 애플리케이션이 순서를 제어할 수 없습니다.

다음 예제에서는 소스 시퀀스에서 AsOrdered 연산자를 사용하여 기본 동작을 재정의합니다. 이렇게 하면 Take 메서드가 소스 시퀀스에서 조건을 충족하는 처음 1000개의 도시를 반환합니다.

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)

그러나 이 쿼리는 전체 파티션에서 원래 순서를 추적하고 병합 시 순서가 일치하는지 확인해야 하므로 순서가 지정되지 않은 버전만큼 빠르게 실행되지 않을 수 있습니다. 따라서 AsOrdered는 필요한 경우에만, 그리고 필요한 쿼리의 파트에만 사용하는 것이 좋습니다. 순서 유지가 더 이상 필요하지 않으면 AsUnordered를 사용하여 기능을 끕니다. 다음 예제에서는 두 개의 쿼리를 작성하여 이 작업을 수행합니다.

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

PLINQ는 나머지 쿼리에 대해 순서 포함 연산자에 의해 생성된 시퀀스의 순서를 유지합니다. 즉, OrderByThenBy 같은 연산자는 뒤에 AsOrdered에 대한 호출이 실행되는 것처럼 처리됩니다.

쿼리 연산자 및 순서 지정

다음 쿼리 연산자는 순서 유지를 쿼리의 모든 후속 작업에 포함하거나 AsUnordered가 호출될 때까지 포함합니다.

다음 PLINQ 쿼리 연산자는 올바른 결과를 생성하기 위해 경우에 따라 순서가 지정된 소스 시퀀스가 필요할 수 있습니다.

일부 PLINQ 쿼리 연산자는 소스 시퀀스의 순서가 지정되거나 순서가 지정되지 않았는지에 따라 다르게 동작합니다. 다음 표에 이러한 연산자가 나와 있습니다.

연산자 소스 시퀀스의 순서가 지정된 경우의 결과 소스 시퀀스의 순서가 지정되지 않은 경우의 결과
Aggregate 비연관 또는 비누적 작업에 대한 비결정적 출력 비연관 또는 비누적 작업에 대한 비결정적 출력
All 해당 없음 해당 없음
Any 해당 없음 해당 없음
AsEnumerable 해당 없음 해당 없음
Average 비연관 또는 비누적 작업에 대한 비결정적 출력 비연관 또는 비누적 작업에 대한 비결정적 출력
Cast 순서가 지정된 결과 순서가 지정되지 않은 결과
Concat 순서가 지정된 결과 순서가 지정되지 않은 결과
Count 해당 없음 해당 없음
DefaultIfEmpty 해당 없음 해당 없음
Distinct 순서가 지정된 결과 순서가 지정되지 않은 결과
ElementAt 지정된 요소 반환 임의 요소
ElementAtOrDefault 지정된 요소 반환 임의 요소
Except 순서가 지정되지 않은 결과 순서가 지정되지 않은 결과
First 지정된 요소 반환 임의 요소
FirstOrDefault 지정된 요소 반환 임의 요소
ForAll 병렬로 비결정적으로 실행 병렬로 비결정적으로 실행
GroupBy 순서가 지정된 결과 순서가 지정되지 않은 결과
GroupJoin 순서가 지정된 결과 순서가 지정되지 않은 결과
Intersect 순서가 지정된 결과 순서가 지정되지 않은 결과
Join 순서가 지정된 결과 순서가 지정되지 않은 결과
Last 지정된 요소 반환 임의 요소
LastOrDefault 지정된 요소 반환 임의 요소
LongCount 해당 없음 해당 없음
Min 해당 없음 해당 없음
OrderBy 시퀀스의 순서 변경 새 순서가 지정된 섹션 시작
OrderByDescending 시퀀스의 순서 변경 새 순서가 지정된 섹션 시작
Range 적용할 수 없음(AsParallel과 동일한 기본값) 해당 없음
Repeat 적용할 수 없음(AsParallel과 동일한 기본값) 해당 없음
Reverse 역방향 순서 지정 아무 작업도 수행하지 않습니다.
Select 순서가 지정된 결과 순서가 지정되지 않은 결과
Select(인덱싱됨) 순서가 지정된 결과 순서가 지정되지 않은 결과.
SelectMany 순서가 지정된 결과. 순서가 지정되지 않은 결과
SelectMany(인덱싱됨) 순서가 지정된 결과. 순서가 지정되지 않은 결과.
SequenceEqual 순서가 지정된 비교 순서가 지정되지 않은 비교
Single 해당 없음 해당 없음
SingleOrDefault 해당 없음 해당 없음
Skip 처음 n개 요소를 건너뜀 임의 n개 요소를 건너뜀
SkipWhile 순서가 지정된 결과. 비결정적. 현재 임의 순서로 SkipWhile 수행
Sum 비연관 또는 비누적 작업에 대한 비결정적 출력 비연관 또는 비누적 작업에 대한 비결정적 출력
Take 처음 n개 요소 사용 임의 n개 요소 사용
TakeWhile 순서가 지정된 결과 비결정적. 현재 임의 순서로 TakeWhile 수행
ThenBy OrderBy 보완 OrderBy 보완
ThenByDescending OrderBy 보완 OrderBy 보완
ToArray 순서가 지정된 결과 순서가 지정되지 않은 결과
ToDictionary 해당 없음 해당 없음
ToList 순서가 지정된 결과 순서가 지정되지 않은 결과
ToLookup 순서가 지정된 결과 순서가 지정되지 않은 결과
Union 순서가 지정된 결과 순서가 지정되지 않은 결과
Where 순서가 지정된 결과 순서가 지정되지 않은 결과
Where(인덱싱됨) 순서가 지정된 결과 순서가 지정되지 않은 결과
Zip 순서가 지정된 결과 순서가 지정되지 않은 결과

순서가 지정되지 않은 결과는 순서가 활발히 바뀌지 않습니다. 특별한 순서 지정 논리가 적용되지 않습니다. 경우에 따라 순서가 지정되지 않은 쿼리가 소스 시퀀스의 순서를 유지할 수 있습니다. 인덱싱된 Select 연산자를 사용하는 쿼리의 경우 PLINQ는 출력 요소가 인덱스 증가 순서로 표시되도록 보장하지만 어떤 인덱스가 어떤 요소에 할당되는지에 대한 보장은 하지 않습니다.

참고 항목