Görev özel durumu işleme

Varsayılan olarak kullanın await . await size doğal istisna akışı sağlar, kodunuzun okunabilirliğini korur ve senkronizasyon-üzerinden-asinronize kilitlenmeleri önler.

Bazen örneğin, eski uyumlu giriş noktalarında bir Task üzerinde engellemeniz gerekebilir. Bu gibi durumlarda, her API'nin özel durumları nasıl ortaya çıkardığınızı anlamanız gerekir.

Engelleyici API'ler için özel durum yayma karşılaştırması

Bir görevi engellemeniz gerektiğinde, özgün özel durum türünü korumak için GetAwaiter().GetResult() kullanın.

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 ve Wait, özel durumları AggregateException içinde sararak özel durum işlemini karmaşıklaştırır. Aşağıdaki kod bu API'leri kullanır ve yanlış özel durum türünü alır:

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

Birden çok özel durumla GetAwaiter().GetResult() hata veren görevler için yine de bir özel durum oluşturur, ancak Task.Exception tüm iç özel durumları içeren bir AggregateException depolar:

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

İki API arasında seçim yaparken bu kılavuzu kullanın:

  • Yapabileceğiniz zaman await tercih edin. Engelleme ve kilitlenme riskini önler.
  • Engellemeniz gerekiyorsa ve özgün özel durum türleri istiyorsanız kullanın GetAwaiter().GetResult(). WinForms uygulamalarında, olay işleyicileri hakkındaki makalenin Yaygın tuzaklar ve kilitlenmeler bölümüne dikkat edin.
  • Mevcut kodunuz AggregateException bekliyorsa, Result veya Wait() kullanın ve InnerExceptions inceleyin.

Bu kurallar yalnızca özel durum şeklini etkiler. Her iki API de geçerli iş parçacığını engeller, bu nedenle her ikisi de tek iş parçacıklı SynchronizationContext ortamlarda kilitlenmeye neden olabilir. Tüm kod yollarında görevlerin düzgün bir şekilde nasıl tamamlanmasını anlamak için bkz. Görevlerinizi tamamlama.

Modern .NET'te gözlemlenmeyen görev istisnaları

Kod özel durumunu gözlemlemeden önce hatalı bir TaskScheduler.UnobservedTaskException sonlandırıldığında, çalışma zamanı Task tarafından harekete geçirilir.

Günümüz .NET'te, gözlemlenmeyen hatalar artık varsayılan olarak süreci çökertmez. Çalışma zamanı bunları bir olay aracılığıyla rapor eder ve ardından yürütmeye devam eder.

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

Tanılama ve telemetri için olayı kullanın. Zaman uyumsuz akışlarda normal özel durum işlemenin yerine olayı kullanmayın.

Ayrıca bakınız