작업 내에서 실행되는 사용자 코드에 의해 throw된 처리되지 않은 예외는 이 항목의 뒷부분에서 설명하는 특정 시나리오를 제외하고는 호출 스레드로 다시 전파됩니다. 정적 또는 인스턴스 Task.Wait 메서드 중 하나를 사용할 때 예외가 전파되며 try/catch 문에 호출을 포함하여 예외를 처리할 수 있습니다. 어떤 작업이 연결된 자식 작업의 부모인 경우 또는 여러 작업에서 대기 중인 경우, 여러 개의 예외가 throw 될 수 있습니다.
모든 예외를 호출 스레드로 다시 전파하기 위해 작업 인프라가 이러한 예외를 AggregateException 인스턴스에서 래핑합니다. AggregateException 예외에는 InnerExceptions 속성이 있으며 이 속성을 열거하면 throw된 모든 원래 예외를 확인하고 각 예외를 개별적으로 처리하거나 처리하지 않을 수 있습니다. 또한 AggragateException.Handle 메서드를 사용하여 원래 예외를 처리할 수도 있습니다.
하나의 예외만 throw된 경우 다음 예제와 같이 AggregateException 예외에서 여전히 래핑됩니다.
public static partial class Program
{
public static void HandleThree()
{
var task = Task.Run(
() => throw new CustomException("This exception is expected!"));
try
{
task.Wait();
}
catch (AggregateException ae)
{
foreach (var ex in ae.InnerExceptions)
{
// Handle the custom exception.
if (ex is CustomException)
{
Console.WriteLine(ex.Message);
}
// Rethrow any other exception.
else
{
throw ex;
}
}
}
}
}
// The example displays the following output:
// This exception is expected!
AggregateException을 catch하고 내부 예외를 관찰하지 않으면 처리되지 않은 예외를 방지할 수 있습니다. 하지만 이 방법은 기본 Exception 유형을 비병렬 시나리오에서 catch하는 것과 유사하기 때문에 사용하지 않는 것이 좋습니다.
Task.Wait 메서드를 호출하여 작업의 완료를 기다리는 것을 원하지 않는 경우 다음 예제가 보여주는 것처럼 작업의 Exception 속성에서 AggregateExcption 예외를 검색할 수도 있습니다. 자세한 내용은 이 문서의 Task.Exception속성을 사용하여 예외 관찰 섹션을 참조하세요.
(아래 예제코드에는 태스크의 Task.IsCompleted 속성을 폴링하여 태스크가 완료된 시기를 결정하는 루프가 포함되어 while 있습니다. 이 작업은 매우 비효율적이므로 프로덕션 코드에서 수행해서는 안 됩니다.)
public static partial class Program
{
public static void HandleFour()
{
var task = Task.Run(
() => throw new CustomException("This exception is expected!"));
while (!task.IsCompleted) { }
if (task.Status == TaskStatus.Faulted)
{
foreach (var ex in task.Exception?.InnerExceptions ?? new(Array.Empty<Exception>()))
{
// Handle the custom exception.
if (ex is CustomException)
{
Console.WriteLine(ex.Message);
}
// Rethrow any other exception.
else
{
throw ex;
}
}
}
}
}
// The example displays the following output:
// This exception is expected!
[연결된 자식 작업 및 중첩된 AggregateExceptions]
작업에 예외를 throw하는 연결된 자식 작업이 있는 경우 해당 예외가 AggregateException에서 래핑된 다음 상위 작업으로 전파되고, 이 상위 작업은 해당 예외를 자체 AggregateException에서 래핑한 다음 호출 스레드로 다시 전파됩니다. 이러한 경우 Task.Wait, WaitAny 또는 WaitAll 메서드에서 catch된 AggregateException예외의 InnerExceptions속성에서 오류를 발생시킨 원래 예외가 아니라 하나 이상의 AggregateException 인스턴스가 포함됩니다. 중첩된 AggregateException 예외를 반복할 필요하 없도록 하려면 AggregateException.InnerExceptions속성에 원래 예외가 포함되도록 Flatten 메서드를 사용하여 중첩된 모든 AggregateException 예외를 제거합니다. 다음 예제에서는 중첩된 AggregateException 인스턴스가 하나의 루프에서 결합되고 처리됩니다.
public static partial class Program
{
public static void FlattenTwo()
{
var task = Task.Factory.StartNew(() =>
{
var child = Task.Factory.StartNew(() =>
{
var grandChild = Task.Factory.StartNew(() =>
{
// This exception is nested inside three AggregateExceptions.
throw new CustomException("Attached child2 faulted.");
}, TaskCreationOptions.AttachedToParent);
// This exception is nested inside two AggregateExceptions.
throw new CustomException("Attached child1 faulted.");
}, TaskCreationOptions.AttachedToParent);
});
try
{
task.Wait();
}
catch (AggregateException ae)
{
foreach (var ex in ae.Flatten().InnerExceptions)
{
if (ex is CustomException)
{
Console.WriteLine(ex.Message);
}
else
{
throw;
}
}
}
}
}
// The example displays the following output:
// Attached child1 faulted.
// Attached child2 faulted.
또한 다음 예제가 보여주는 것처럼 AggregateException.Flatten 메서드를 사용하여 단일 AggregateException 인스턴스에서 여러 작업에 의해 throw된 여러 AggregateException인스턴스의 내부 예외를 다시 throw할 수 있습니다.
public static partial class Program
{
public static void TaskExceptionTwo()
{
try
{
ExecuteTasks();
}
catch (AggregateException ae)
{
foreach (var e in ae.InnerExceptions)
{
Console.WriteLine(
"{0}:\n {1}", e.GetType().Name, e.Message);
}
}
}
static void ExecuteTasks()
{
// Assume this is a user-entered String.
string path = @"C:\";
List<Task> tasks = new();
tasks.Add(Task.Run(() =>
{
// This should throw an UnauthorizedAccessException.
return Directory.GetFiles(
path, "*.txt",
SearchOption.AllDirectories);
}));
tasks.Add(Task.Run(() =>
{
if (path == @"C:\")
{
throw new ArgumentException(
"The system root is not a valid path.");
}
return new string[] { ".txt", ".dll", ".exe", ".bin", ".dat" };
}));
tasks.Add(Task.Run(() =>
{
throw new NotImplementedException(
"This operation has not been implemented.");
}));
try
{
Task.WaitAll(tasks.ToArray());
}
catch (AggregateException ae)
{
throw ae.Flatten();
}
}
}
// The example displays the following output:
// UnauthorizedAccessException:
// Access to the path 'C:\Documents and Settings' is denied.
// ArgumentException:
// The system root is not a valid path.
// NotImplementedException:
// This operation has not been implemented.
[분리된 자식 작업의 예외]
기본적으로 자식 작업은 분리된 작업으로 만들어집니다. 분리된 작업에서 throw된 예외는 직계 부모 작업에서 처리되거나 다시 throw되어야 하지만, 연결된 자식 작업이 다시 전파되는 것과 동일한 방식으로 호출 스레드에 다시 전파되지는 않습니다. 최상위 부모는 분리된 자식의 예외를 수동으로 다시 throw하여 AggregateException에서 래핑하고 호출 스레드로 다시 전파할 수 있습니다.
public static partial class Program
{
public static void DetachedTwo()
{
var task = Task.Run(() =>
{
var nestedTask = Task.Run(
() => throw new CustomException("Detached child task faulted."));
// Here the exception will be escalated back to the calling thread.
// We could use try/catch here to prevent that.
nestedTask.Wait();
});
try
{
task.Wait();
}
catch (AggregateException ae)
{
foreach (var e in ae.Flatten().InnerExceptions)
{
if (e is CustomException)
{
Console.WriteLine(e.Message);
}
}
}
}
}
// The example displays the following output:
// Detached child task faulted.
'C#' 카테고리의 다른 글
Task cancellation (0) | 2023.01.23 |
---|---|
Task Parallel Library에 대한 예외 처리2 (0) | 2023.01.23 |
[DevExpress] AccordionControl Group Header에 Check Box 넣기 (0) | 2023.01.23 |
[DevExpress] Hot-Tracking 코드로 직접 구현하기 (0) | 2023.01.19 |
HttpWebRequest 클래스를 활용한 POST 전송 (0) | 2023.01.18 |