작업 병렬화 Task Parallelism
작업 병렬화는 일반적으로 생각하는 병렬 처리를 하는 방식입니다. 작업 병렬화는 데이터 병렬화와 달리, 데이터마다 동일한 처리를 하는 방법이 아닌, 독립적인 작업들을 동시에 처리하는 방식입니다. 데이터 병렬화와 마찬가지로 작업 병렬화도 TPL이 개발자를 대신하여 스레드를 관리합니다. 따라서 개발자는 병렬 프로그래밍을 Parallel.Invoke 메서도를 사용해 아래와 같이 손쉽게 구현할 수 있습니다.
Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());
예제 코드는 Parallel.Invoke 메서드로 DoSomeWork() 과 DoSomeOtherWork()을 병렬로 처리하고 있습니다. 코드의 내부 동작을 추측할 때, 막연히 Parallel.Invoke 메서드에 전달된 Action 수만큼 스레드가 생성될거라 추측하기 쉽습니다. 하지만 TPL이 내부적으로 스레드를 관리해주는 역할을 하므로, TPL의 판단에 따라 스레드 수는 얼마든지 달라질 수 있습니다.
병렬 처리 제어하기
만약 병렬 작업을 제어하길 원한다면 명시적으로 Task 클래스를 활용하면 된다. Task 클래스로 생성된 작업은 스케줄러에 의해 같은 스레드 내에서 비동기로 처리되거나, 새로운 스레드에서 병렬 처리됩니다.
Task 클래스는 비동기 프로그래밍에도 활용됩니다.
연속하여 작업 처리하기
Task.ContinueWith
작업 완료 기다리기
Task.Wait
작업 중첩 시 주의할 점
작업 내에 작업을 생성하여 처리하면 작업을 중첩하여 처리하게 됩니다. 일반적인 순차 동기 코드에선 외부 코드는 내부 코드가 완료되어야 완료될 수 있습니다. 하지만 비동기 병렬 작업에선 외부 작업이 내부 작업을 기다리지 않고 종료됩니다. 이러한 특징은 연속 작업에도 영향을 미칩니다. 연속 작업은 내부 작업을 기다리지 않고 외부 작업이 완료되면 바로 시작하게 됩니다.만약 외부 작업과 함께 내부 작업의 완료를 기다리고 싶다면, TaskCreationOptios.AttachedToPaarent를 사용하면 됩니다. 이때 연속 작업도 또한 내부 작업 완료를 기다리게 됩니다.
여러 작업을 묶어서 구성하기
Task.WhenAllTask.WhenAny
Task.WhenAny / Task.WhenAll 과 Task.WaitAny / Task.WaitAll 의 차이는 스레드의 코드 실행을 막는지 여부입니다. When 메서드는 Task 객체를 반환하기 때문에 await 키워드로 비동기 흐름을 만들 수 있지만, Wait 메서드는 작
업이 완료되길 기다려야 합니다.
작업에서 발생한 예외 처리하기
작업 내에서 예외가 발생하면 작업을 호출한 스레드로 예외가 전달됩니다. 예외는 AggregateException로 래핑되어 전달됩니다. AggregateException의 InnerExceptions 속성으로 실제 발생한 예외를 확인할 수 있습니다.
var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );
try
{
task1.Wait();
}
catch (AggregateException ae)
{
foreach (var e in ae.InnerExceptions) {
// 작업으로부터 전파된 예외 처리
if (e is CustomException) {
Console.WriteLine(e.Message);
}
// 처리되지 않은 예외는 다시 던진다.
else {
throw;
}
}
}
만약 중첩된 작업에서 발생한 예의로 인해 AggregateException도 중첩되면, 예외를 처리하는 코드 구간에서 전판된 AggregateException 객체의 Flatten 메서드를 호출하면 중첩을 없앨 수 있습니다.
중첩된 작업 구조에서 자식 작업이 부모 작업과 분리되어 생성되었다면 예외가 누락될 수 있으니 주의바랍니다. 자식 작업은 부모 작업과 연결하지 않으면 분리되어 생성됩니다.
작업 취소하기
병렬 프로그래밍이든 비동기 프로그래밍이든 Task 클래스를 기반으로 작업을 관리합니다. 따라서 작업을 취소하는 방법도 동일합니다. .Net에서 작업 취소는 취소 토큰을 통해 이루어집니다. 취소 코튼은 CancellationTokenSource 클래스로 생성할 수 있습니다. 아래 순서로 작업 취소를 위한 코드를 작성하시면 됩니다.
- CancellationTokenSource 개체를 생성합니다.
- 작업 취소에 영향을 받는 모든 작업에 CancellationTokenSource.Token 속성을 전달합니다.
- 각 작업 별로 취소에 응답하는 코드를 작성합니다.
- CancellationTokenSource.Cancel 메서드를 호출해 작업 취소를 시작합니다.
CancellationTokenSource 클래스는 개발자가 직접 Dispose 하여 자원을 해제해야 합니다.
각 작업 별로 취소에 응답하려면 CancellationTokenSource 개체로부터 전달된 CancellationToken.IsCancellationRequested 속성 값을 확인해야 합니다. 만약 취소가 어디서부터 시작되었는지 파악하길 원한다면 OperationCanceledException 예외를 던지면 됩니다.
var tokenSource = new CancellationTokenSource();
CancellationToken ct = tokenSource.Token;
var task = Task.Run(() =>
{
bool moreToDo = true;
while (moreToDo)
{
if (ct.IsCancellationRequested)
{
ct.ThrowIfCancellationRequested();
}
}
}, tokenSource.Token);
tokenSource.Cancel();
try
{
await task;
}
catch (OperationCanceledException e)
{
}
finally
{
tokenSource2.Dispose();
}
[출처]
'C#' 카테고리의 다른 글
리플렉션 Reflection (1) (0) | 2023.11.04 |
---|---|
덤프 파일 사용 방벙(.pdb 디버깅) (0) | 2023.11.04 |
병렬 프로그래밍 Parallel Programming (1) - 데이터 병렬화 (0) | 2023.11.04 |
운영체제에서 제공하는 다양한 동기화 장치들에 대한 설명 (0) | 2023.11.04 |
Task와 Thread 차이 (0) | 2023.11.04 |