Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Gunakan await sebagai default Anda.
await memberikan alur pengecualian yang alami, menjaga kode Anda tetap mudah dibaca, dan menghindari deadlock sinkronisasi-di-atas-asinkron.
Terkadang Anda masih perlu memblokir Task, misalnya, di titik masuk sinkron warisan. Dalam kasus tersebut, Anda perlu memahami bagaimana setiap API menampilkan pengecualian.
Membandingkan propagasi pengecualian untuk memblokir API
Ketika Anda harus memblokir tugas, gunakan GetAwaiter(). GetResult() untuk mempertahankan jenis pengecualian asli:
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 dan Wait membungkus pengecualian dalam AggregateException, yang mempersulit penanganan pengecualian. Kode berikut menggunakan API ini dan menerima jenis pengecualian yang salah:
// ⚠️ 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
Untuk tugas yang mengalami kesalahan dengan beberapa pengecualian, GetAwaiter().GetResult() tetap melemparkan satu pengecualian, tetapi Task.Exception menyimpan AggregateException yang berisi semua pengecualian dalamnya.
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 Vs GetAwaiter().GetResult()
Gunakan panduan ini saat Anda memilih antara dua API:
- Utamakan
awaitjika memungkinkan. Ini menghindari risiko pemblokiran dan kebuntuan. - Jika Anda harus memblokir dan Anda menginginkan jenis pengecualian asli, gunakan
GetAwaiter().GetResult(). Di aplikasi WinForms, perhatikan bagian Jebakan umum dan kebuntuan artikel tentang penanganan aktivitas. - Jika kode Anda yang ada mengharapkan AggregateException, gunakan
ResultatauWait()dan periksaInnerExceptions.
Aturan ini hanya memengaruhi bentuk pengecualian. Kedua API masih memblokir utas saat ini, sehingga keduanya dapat kebuntuan pada lingkungan utas SynchronizationContext tunggal. Untuk memahami cara menyelesaikan tugas dengan benar di semua jalur kode, lihat Menyelesaikan tugas Anda.
Pengecualian tugas yang tidak teramati dalam .NET modern
Runtime memunculkan TaskScheduler.UnobservedTaskException ketika Task yang mengalami kesalahan dinyatakan sudah selesai sebelum kode mengamati pengecualiannya.
Dalam .NET modern, pengecualian tidak tertangkap tidak lagi merusak proses secara bawaan. Runtime melaporkannya melalui event, lalu melanjutkan proses eksekusi.
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
Gunakan peristiwa untuk diagnostik dan telemetri. Jangan gunakan peristiwa sebagai pengganti penanganan pengecualian normal dalam alur asinkron.