Feladatkivétel-kezelés

Használja await alapértelmezettként. await természetes kivételfolyamatot biztosít, olvashatóvá teszi a kódot, és elkerüli az aszinkron holtpontok szinkronizálását.

Néha még mindig blokkolnia kell egy Task, például egy régebbi szinkron belépési pontnál. Ezekben az esetekben tisztában kell lenni azzal, hogy az egyes API-k hogyan jelenítik meg a kivételeket.

Az API-k blokkolása során történő kivételpropagálás összehasonlítása

Ha le kell tiltania egy tevékenységet, használja a () elemet GetAwaiter. GetResult() az eredeti kivételtípus megőrzéséhez:

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 és Wait becsomagolja a kivételeket AggregateException, ami bonyolítja a kivételkezelést. A következő kód ezeket az API-kat használja, és nem a megfelelő kivételtípust kapja:

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

Több kivétellel hibát okozó feladatok esetében a GetAwaiter().GetResult() továbbra is egyetlen kivételt dob, de a Task.Exception egy AggregateException-t tárol, amely az összes belső kivételt tartalmazza.

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

Ezt az útmutatót akkor használja, ha a két API közül választ:

  • Részesítsd előnyben a await, ha lehet. Elkerüli a blokkolást és a holtpont kockázatát.
  • Ha le kell tiltania, és eredeti kivételtípusokat szeretne, használja a következőt GetAwaiter().GetResult(): . A WinForms-alkalmazásokban jegyezze fel az eseménykezelőkről szóló cikk Gyakori buktatók és holtpontok szakaszát.
  • Ha a meglévő kód elvárja AggregateException, akkor használja Result vagy Wait() és vizsgálja meg InnerExceptions.

Ezek a szabályok csak a kivételalakzatokat érintik. Mindkét API továbbra is blokkolja az aktuális szálat, így mindkettő holtpontot jelenthet az egyszálas SynchronizationContext környezetekben. Ha szeretné megtudni, hogyan végezheti el megfelelően a feladatokat az összes kódútvonalon, olvassa el a Tevékenységek befejezése című témakört.

Nem figyelt feladatkivétel a modern .NET-ben

A futtatókörnyezet kivételt emel TaskScheduler.UnobservedTaskException, amikor egy hibás állapotú Task véglegesítve lesz, mielőtt a kód észleli a kivételét.

A modern .NET, a nem észlelt kivételek alapértelmezés szerint nem okoznak folyamatösszeomlást. A futtatókörnyezet az esemény hatására jelenti őket, majd folytatja a program futását.

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

Használja az eseményt diagnosztikához és telemetriához. Ne használja az eseményt az aszinkron folyamatok normál kivételkezelésének helyettesítésére.

Lásd még