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とWaitAggregateExceptionで例外をラップします。例外処理が複雑になります。 次のコードでは、これらの 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() は引き続き 1 つの例外をスローしますが、 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()
次の 2 つの API のいずれかを選択する場合は、次のガイダンスを使用します。
- 可能な場合は
awaitを優先します。 ブロックとデッドロックのリスクを回避します。 - ブロックする必要があり、元の例外の種類が必要な場合は、
GetAwaiter().GetResult()を使用します。 WinForms アプリケーションでは、イベント ハンドラーに関する記事の 「一般的な落とし穴とデッドロック 」セクションに注意してください。 - 既存のコードで AggregateExceptionが必要な場合は、
ResultまたはWait()を使用して、InnerExceptionsを検査します。
これらのルールは、例外の図形にのみ影響します。 どちらの API も現在のスレッドをブロックしているため、シングルスレッド SynchronizationContext 環境で両方がデッドロックする可能性があります。 すべてのコード パスでタスクを適切に完了する方法については、「 タスクを完了する」を参照してください。
最新の.NETにおける未監視タスクの例外
コードが例外を観察する前に、エラーが発生した Task が終了すると、ランタイムは TaskScheduler.UnobservedTaskException を発生させます。
最新の.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