방프리

24.04.03 Chapter4. 병렬 처리 (Item 36) 본문

C#/More Effective C#

24.04.03 Chapter4. 병렬 처리 (Item 36)

방프리 2024. 4. 3. 23:12

Item 36 : 예외를 염두에 두고 병렬 알고리즘을 만들라

PLINQ는 자식 스레드가 잘못 동작하는 가능성에는 고려하지 않았는데 실제로는 언제든지 문제가 발생할 수 있다.
단 이 처리의 몫은 항상 개발자가 져야 한다. 예외는 스레드의 경계를 넘어 다른 스레드에 전달될 수 없다.

예외가 발생할 경우 예외를 백그라운드 작업을 수행하는 태스크를 떠나지 않게 해야된다.
예로 TaskCompletionSource<> 클래스를 사용할 경우에는 TrySetException()을 호출해서는 안되고 태스크의 완료
상황을 나타내기 위해 TrySetResult()를 어떻게든 호출해야 한다.

private static Task<byte[]> startDownload(string url)
{
    var tcs = new TaskCompletionSource<byte[]>(url);
    var wc = new WebClient();
    wc.DownloadDataCompleted += (sender, e) =>
    {
        if (e.UserState == tcs)
        {
            if (e.Cancelled == true)
            {
                tcs.TrySetCancelled();
            }
        }
        else if (e.Error != null)
        {
            if (e.Error is WebException)
                tcs.TrySetResult(new byte[0]);
            else 
                tcs.TrySetException(e.Error);
        }
        else
            tcs.TrySetResult(e.Result);
    };
    wc.DownloadDataAsync(new Uri(url), tcs);
    return tcs.Task;
}

PLINQ를 사용하면 모든 경우에 지연 평가를 수행한다. 일반적으로 쿼리는 다른 코드가 해당 쿼리의 결과를 요청할 때 실행되는데 PLINQ는 백그라운드 스레드들이 실행되어 결과를 생성하며, 다른 태스크가 최종 결과 시퀸스를 생성한다.

그래서 전형적인 LINQ 쿼리에서는 쿼리 결과를 사용하는 코드 주변만을 try/catch 블록으로 감싸되 LINQ 쿼리식을 정의하는 코드 주변을 감쌀 필요가 없었지만 PLINQ는 쿼리 선언부까지 감싸야 한다. 물론 예외는 AggregateException을 잡아야 한다.

최선의 대책은 병렬 태스크를 실행하는 코드에서 절대 예외를 던지지 않게 하는 것이다. 하지만 그렇게 한다고 해도 예상하지 못한 예외가 어디선가 발생할 수 있기에 모든 백그라운드 작업을 시작한 제어 스레드는 반드시 AggregateException을 처리해야 한다.

Comments