기본값으로 사용합니다 await .
await 는 자연스러운 예외 흐름을 제공하고, 코드를 읽을 수 있게 유지하며, 비동기 동기화 교착 상태를 방지합니다.
레거시 동기 진입점에서 여전히 Task을(를) 차단해야 하는 경우가 있습니다. 이러한 경우 각 API가 예외를 노출하는 방법을 이해해야 합니다.
차단 API에 대한 예외 전파 비교
작업을 차단해야 하는 경우 ()를 사용합니다 GetAwaiter. 원래 예외 형식을 유지하려면 GetResult()를 사용합니다.
public static class SingleExceptionExample
{
public static Task<int> FaultAsync()
{
return Task.FromException<int>(new InvalidOperationException("Single failure"));
}
public static void ShowBlockingDifferences()
{
try
{
_ = FaultAsync().GetAwaiter().GetResult();
}
catch (Exception ex)
{
Console.WriteLine($"GetAwaiter().GetResult() threw {ex.GetType().Name}");
}
}
}
Public Module SingleExceptionExample
Public Function FaultAsync() As Task(Of Integer)
Return Task.FromException(Of Integer)(New InvalidOperationException("Single failure"))
End Function
Public Sub ShowBlockingDifferences()
Try
Dim ignored = FaultAsync().GetAwaiter().GetResult()
Catch ex As Exception
Console.WriteLine($"GetAwaiter().GetResult() threw {ex.GetType().Name}")
End Try
End Sub
End Module
Task<TResult>.Result 에서 Wait 예외를 AggregateException래핑하여 예외 처리를 복잡하게 만듭니다. 다음 코드는 이러한 API를 사용하고 잘못된 예외 유형을 받습니다.
// ⚠️ DON'T copy this snippet. It demonstrates a problem where exceptions get wrapped unnecessarily.
public static class SingleExceptionBadExample
{
public static Task<int> FaultAsync()
{
return Task.FromException<int>(new InvalidOperationException("Single failure"));
}
public static void ShowBlockingDifferences()
{
try
{
_ = FaultAsync().Result;
}
catch (AggregateException ex)
{
Console.WriteLine($".Result threw {ex.GetType().Name} with inner {ex.InnerException?.GetType().Name}");
}
}
}
' ⚠️ DON'T copy this snippet. It demonstrates a problem where exceptions get wrapped unnecessarily.
Public Module SingleExceptionBadExample
Public Function FaultAsync() As Task(Of Integer)
Return Task.FromException(Of Integer)(New InvalidOperationException("Single failure"))
End Function
Public Sub ShowBlockingDifferences()
Try
Dim ignored = FaultAsync().Result
Catch ex As AggregateException
Console.WriteLine($".Result threw {ex.GetType().Name} with inner {ex.InnerException?.GetType().Name}")
End Try
End Sub
End Module
여러 예외에서 오류가 발생한 작업의 경우, GetAwaiter().GetResult()는 여전히 하나의 예외를 던지는 반면, Task.Exception는 모든 내부 예외를 포함하는 AggregateException를 저장합니다.
public static class MultiExceptionExample
{
public static async Task FaultAfterDelayAsync(string name, int milliseconds)
{
await Task.Delay(milliseconds);
throw new InvalidOperationException($"{name} failed");
}
public static void ShowMultipleExceptions()
{
Task combined = Task.WhenAll(
FaultAfterDelayAsync("First", 10),
FaultAfterDelayAsync("Second", 20));
try
{
combined.GetAwaiter().GetResult();
}
catch (Exception ex)
{
Console.WriteLine($"GetAwaiter().GetResult() surfaced: {ex.Message}");
}
if (combined.IsFaulted && combined.Exception is not null)
{
AggregateException allErrors = combined.Exception.Flatten();
Console.WriteLine($"Task.Exception contains {allErrors.InnerExceptions.Count} exceptions.");
}
else
{
Console.WriteLine("Task.Exception is null because the task didn't fault.");
}
}
}
Public Module MultiExceptionExample
Public Async Function FaultAfterDelayAsync(name As String, milliseconds As Integer) As Task
Await Task.Delay(milliseconds)
Throw New InvalidOperationException($"{name} failed")
End Function
Public Sub ShowMultipleExceptions()
Dim combined As Task = Task.WhenAll(
FaultAfterDelayAsync("First", 10),
FaultAfterDelayAsync("Second", 20))
Try
combined.GetAwaiter().GetResult()
Catch ex As Exception
Console.WriteLine($"GetAwaiter().GetResult() surfaced: {ex.Message}")
End Try
If combined.IsFaulted AndAlso combined.Exception IsNot Nothing Then
Dim allErrors As AggregateException = combined.Exception.Flatten()
Console.WriteLine($"Task.Exception contains {allErrors.InnerExceptions.Count} exceptions.")
Else
Console.WriteLine("Task.Exception was not available because the task did not fault.")
End If
End Sub
End Module
Task.Result 및 GetAwaiter().GetResult()
다음 두 API 중에서 선택할 때 이 지침을 사용합니다.
- 가능한 경우
await를 선호하십시오. 차단 및 교착 상태 위험을 방지합니다. - 차단해야 하고 원래 예외 형식
GetAwaiter().GetResult()을 원하는 경우 . WinForms 애플리케이션에서 이벤트 처리기에 대한 문서의 일반적인 문제 및 교착 상태 섹션을 확인합니다. - 기존 코드가 AggregateException를 예상한다면
Result이나Wait()를 사용하고InnerExceptions를 확인하십시오.
이러한 규칙은 예외 셰이프에만 영향을 미칩니다. 두 API 모두 여전히 현재 스레드를 차단하므로 둘 다 단일 스레드 SynchronizationContext 환경에서 교착 상태가 될 수 있습니다. 모든 코드 경로에서 작업을 제대로 완료하는 방법을 이해하려면 작업 완료를 참조하세요.
관찰되지 않은 최신 .NET 작업 내 예외
TaskScheduler.UnobservedTaskException가 코드가 예외를 감지하기 전에 오류가 난 Task이(finalized) 완료되면 런타임이 발생합니다.
최신 .NET 관찰되지 않은 예외는 더 이상 기본적으로 프로세스에 충돌하지 않습니다. 런타임은 이벤트를 통해 이를 보고한 후 실행을 지속합니다.
public static class UnobservedTaskExceptionExample
{
public static void ShowEventBehavior()
{
bool eventRaised = false;
TaskScheduler.UnobservedTaskException += (_, args) =>
{
eventRaised = true;
Console.WriteLine($"UnobservedTaskException raised with {args.Exception.InnerExceptions.Count} exception(s).");
args.SetObserved();
};
_ = Task.Run(() => throw new ApplicationException("Background failure"));
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine(eventRaised
? "Event was raised. The process continued."
: "Event was not observed in this short run. The process still continued.");
}
}
Public Module UnobservedTaskExceptionExample
Public Sub ShowEventBehavior()
Dim eventRaised As Boolean = False
AddHandler TaskScheduler.UnobservedTaskException,
Sub(sender, args)
eventRaised = True
Console.WriteLine($"UnobservedTaskException raised with {args.Exception.InnerExceptions.Count} exception(s).")
args.SetObserved()
End Sub
Task.Run(Sub() Throw New ApplicationException("Background failure"))
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
If eventRaised Then
Console.WriteLine("Event was raised. The process continued.")
Else
Console.WriteLine("Event was not observed in this short run. The process still continued.")
End If
End Sub
End Module
진단 및 원격 분석에 이벤트를 사용합니다. 비동기 흐름에서 일반적인 예외 처리를 대신하는 이벤트로 사용하지 마세요.
참고하십시오
.NET