Afhandeling van taakonderzondering

Gebruik await als uw standaard. await biedt een natuurlijk uitzonderingsverloop, houdt uw code leesbaar en voorkomt synchronisatie-over-asynchrone deadlocks.

Soms moet u nog steeds blokkeren op een Task, bijvoorbeeld in verouderde synchrone toegangspunten. In die gevallen moet u begrijpen hoe elke API uitzonderingen weer krijgt.

Uitzonderingsdoorgifte vergelijken voor blokkerende API's

Wanneer u een taak moet blokkeren, gebruik GetAwaiter().GetResult() om het oorspronkelijke uitzonderingstype te behouden:

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 en Wait wikkel uitzonderingen in AggregateException, waardoor de verwerking van uitzonderingen ingewikkeld is. De volgende code maakt gebruik van deze API's en ontvangt het verkeerde uitzonderingstype:

// ⚠️ 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

Voor taken die gepaard gaan met meerdere uitzonderingen, GetAwaiter().GetResult() gooit nog steeds één uitzondering, maar Task.Exception slaat een AggregateException op die alle binnenste uitzonderingen bevat.

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 versus GetAwaiter().GetResult()

Gebruik deze richtlijnen wanneer u kiest tussen de twee API's:

  • Gebruik await wanneer je dat kunt. Het voorkomt blokkerings- en impasserisico's.
  • Als u moet blokkeren en u oorspronkelijke uitzonderingstypen wilt, gebruikt u GetAwaiter().GetResult(). In WinForms-toepassingen, gelieve op te merken de sectie Veelvoorkomende valkuilen en impasses van het artikel over gebeurtenishandlers.
  • Als uw bestaande code AggregateException verwacht, gebruik dan Result of Wait() en inspecteer InnerExceptions.

Deze regels zijn alleen van invloed op de uitzonderingsvorm. Beide API's blokkeren nog steeds de huidige thread, dus beide kunnen deadlocks veroorzaken in een single-threaded SynchronizationContext omgeving. Als u wilt weten hoe u taken op alle codepaden correct kunt voltooien, raadpleegt u Taken voltooien.

Niet-geobserveerde taak-uitzonderingen in moderne .NET

De runtime veroorzaakt TaskScheduler.UnobservedTaskException wanneer een in een fouttoestand verkerende Task wordt afgehandeld voordat de code de uitzondering waarneemt.

In moderne .NET crashen niet-geobserveerde uitzonderingen het proces niet meer standaard. De runtime rapporteert deze via de gebeurtenis en gaat vervolgens door met de uitvoering.

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

Gebruik de gebeurtenis voor diagnostische gegevens en telemetrie. Gebruik de gebeurtenis niet als vervanging voor normale verwerking van uitzonderingen in asynchrone stromen.

Zie ook