Obsługa wyjątków (biblioteka równoległa zadań)

Nieobsługiwane wyjątki zgłaszane przez kod użytkownika uruchomiony wewnątrz zadania są propagowane z powrotem do wątku wywołującego, z wyjątkiem niektórych scenariuszy opisanych w dalszej części tego tematu. Wyjątki są propagowane podczas korzystania z jednej ze statycznych lub metod wystąpienia Task.Wait i są obsługiwane przez dołączenie wywołania w instrukcji/trycatch . Jeśli zadanie jest elementem nadrzędnym dołączonych zadań podrzędnych lub jeśli oczekujesz na wiele zadań, może zostać zgłoszonych wiele wyjątków.

Aby propagować wszystkie wyjątki z powrotem do wątku wywołującego, infrastruktura zadań opakowuje je w wystąpieniu AggregateException . Wyjątek AggregateException ma InnerExceptions właściwość, którą można wyliczyć w celu zbadania wszystkich zgłaszanych oryginalnych wyjątków i obsługi (lub nieobsługiwanej) poszczególnych wyjątków. Można również obsługiwać oryginalne wyjątki przy użyciu AggregateException.Handle metody .

Nawet jeśli zgłaszany jest tylko jeden wyjątek, nadal jest opakowany w AggregateException wyjątek, jak pokazano w poniższym przykładzie.


public static partial class Program
{
    public static void HandleThree()
    {
        var task = Task.Run(
            () => throw new CustomException("This exception is expected!"));

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            foreach (var ex in ae.InnerExceptions)
            {
                // Handle the custom exception.
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                }
                // Rethrow any other exception.
                else
                {
                    throw ex;
                }
            }
        }
    }
}
// The example displays the following output:
//        This exception is expected!
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))

        Try
            task1.Wait()
        Catch ae As AggregateException
            For Each ex In ae.InnerExceptions
                ' Handle the custom exception.
                If TypeOf ex Is CustomException Then
                    Console.WriteLine(ex.Message)
                    ' Rethrow any other exception.
                Else
                    Throw ex
                End If
            Next
        End Try
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays the following output:
'       This exception is expected!

Można uniknąć nieobsługiwanego wyjątku, przechwytując element AggregateException i nie obserwując żadnego z wyjątków wewnętrznych. Zalecamy jednak, aby tego nie robić, ponieważ jest on analogiczny do przechwytywania typu podstawowego Exception w scenariuszach innych niż równoległe. Aby przechwycić wyjątek bez wykonywania określonych akcji w celu odzyskania z niego, może pozostawić program w nieokreślonym stanie.

Jeśli nie chcesz wywoływać Task.Wait metody w celu oczekiwania na ukończenie zadania, możesz również pobrać AggregateException wyjątek z właściwości zadania Exception , jak pokazano w poniższym przykładzie. Aby uzyskać więcej informacji, zobacz sekcję Obserwowanie wyjątków przy użyciu właściwości Task.Exception w tym temacie.


public static partial class Program
{
    public static void HandleFour()
    {
        var task = Task.Run(
            () => throw new CustomException("This exception is expected!"));

        while (!task.IsCompleted) { }

        if (task.Status == TaskStatus.Faulted)
        {
            foreach (var ex in task.Exception?.InnerExceptions ?? new(Array.Empty<Exception>()))
            {
                // Handle the custom exception.
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                }
                // Rethrow any other exception.
                else
                {
                    throw ex;
                }
            }
        }
    }
}
// The example displays the following output:
//        This exception is expected!
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))

        While Not task1.IsCompleted
        End While

        If task1.Status = TaskStatus.Faulted Then
            For Each ex In task1.Exception.InnerExceptions
                ' Handle the custom exception.
                If TypeOf ex Is CustomException Then
                    Console.WriteLine(ex.Message)
                    ' Rethrow any other exception.
                Else
                    Throw ex
                End If
            Next
        End If
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays the following output:
'       This exception is expected!

Uwaga

Powyższy przykładowy kod zawiera pętlę while , która sonduje właściwość zadania Task.IsCompleted w celu określenia, kiedy zadanie zostało ukończone. Nie należy tego robić w kodzie produkcyjnym, ponieważ jest bardzo nieefektywny.

Jeśli nie zaczekasz na zadanie, które propaguje wyjątek lub uzyskujesz dostęp do jego Exception właściwości, wyjątek jest eskalowany zgodnie z zasadami wyjątków platformy .NET, gdy zadanie jest zbierane przez śmieci.

Jeśli wyjątki mogą przechodzić z powrotem do wątku przyłączania, możliwe, że zadanie może nadal przetwarzać niektóre elementy po wystąpieniu wyjątku.

Uwaga

Po włączeniu opcji "Tylko mój kod" program Visual Studio w niektórych przypadkach przerwie w wierszu, który zgłasza wyjątek i wyświetla komunikat o błędzie z komunikatem "wyjątek nie jest obsługiwany przez kod użytkownika". Ten błąd jest łagodny. Możesz nacisnąć klawisz F5, aby kontynuować i zobaczyć zachowanie obsługi wyjątków, które przedstawiono w tych przykładach. Aby zapobiec uszkodzeniu pierwszego błędu programu Visual Studio, usuń zaznaczenie pola wyboru Włącz tylko mój kod w obszarze Narzędzia, Opcje, Debugowanie, Ogólne.

Dołączone zadania podrzędne i zagnieżdżone zagnieżdżone wartości AggregateExceptions

Jeśli zadanie ma dołączone zadanie podrzędne, które zgłasza wyjątek, ten wyjątek jest owinięty przed AggregateException jego propagacją do zadania nadrzędnego, które opakowuje ten wyjątek samodzielnie AggregateException przed propagacją go z powrotem do wątek wywołujący. W takich przypadkach właściwość wyjątku przechwyconego InnerExceptions w metodzie Task.Wait, WaitAnylub WaitAll zawiera co najmniej jedno AggregateException wystąpienie, a nie oryginalne wyjątki, które spowodowały błąd.AggregateException Aby uniknąć konieczności iterowania zagnieżdżonych AggregateException wyjątków, możesz użyć Flatten metody , aby usunąć wszystkie wyjątki zagnieżdżone AggregateException , aby AggregateException.InnerExceptions właściwość zawierała oryginalne wyjątki. W poniższym przykładzie zagnieżdżone AggregateException wystąpienia są spłaszczone i obsługiwane w jednej pętli.


public static partial class Program
{
    public static void FlattenTwo()
    {
        var task = Task.Factory.StartNew(() =>
        {
            var child = Task.Factory.StartNew(() =>
            {
                var grandChild = Task.Factory.StartNew(() =>
                {
                    // This exception is nested inside three AggregateExceptions.
                    throw new CustomException("Attached child2 faulted.");
                }, TaskCreationOptions.AttachedToParent);

                // This exception is nested inside two AggregateExceptions.
                throw new CustomException("Attached child1 faulted.");
            }, TaskCreationOptions.AttachedToParent);
        });

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            foreach (var ex in ae.Flatten().InnerExceptions)
            {
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                }
                else
                {
                    throw;
                }
            }
        }
    }
}
// The example displays the following output:
//    Attached child1 faulted.
//    Attached child2 faulted.
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Factory.StartNew(Sub()
                                              Dim child1 = Task.Factory.StartNew(Sub()
                                                                                     Dim child2 = Task.Factory.StartNew(Sub()
                                                                                                                            Throw New CustomException("Attached child2 faulted.")
                                                                                                                        End Sub,
                                                                                                                        TaskCreationOptions.AttachedToParent)
                                                                                     Throw New CustomException("Attached child1 faulted.")
                                                                                 End Sub,
                                                                                 TaskCreationOptions.AttachedToParent)
                                          End Sub)

        Try
            task1.Wait()
        Catch ae As AggregateException
            For Each ex In ae.Flatten().InnerExceptions
                If TypeOf ex Is CustomException Then
                    Console.WriteLine(ex.Message)
                Else
                    Throw
                End If
            Next
        End Try
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays the following output:
'       Attached child1 faulted.
'       Attached child2 faulted.

Można również użyć AggregateException.Flatten metody , aby ponownie wprowadzić wyjątki wewnętrzne z wielu wystąpień zgłaszanych przez wiele AggregateException zadań w jednym AggregateException wystąpieniu, jak pokazano w poniższym przykładzie.

public static partial class Program
{
    public static void TaskExceptionTwo()
    {
        try
        {
            ExecuteTasks();
        }
        catch (AggregateException ae)
        {
            foreach (var e in ae.InnerExceptions)
            {
                Console.WriteLine(
                    "{0}:\n   {1}", e.GetType().Name, e.Message);
            }
        }
    }

    static void ExecuteTasks()
    {
        // Assume this is a user-entered String.
        string path = @"C:\";
        List<Task> tasks = new();

        tasks.Add(Task.Run(() =>
        {
            // This should throw an UnauthorizedAccessException.
            return Directory.GetFiles(
                path, "*.txt",
                SearchOption.AllDirectories);
        }));

        tasks.Add(Task.Run(() =>
        {
            if (path == @"C:\")
            {
                throw new ArgumentException(
                    "The system root is not a valid path.");
            }
            return new string[] { ".txt", ".dll", ".exe", ".bin", ".dat" };
        }));

        tasks.Add(Task.Run(() =>
        {
            throw new NotImplementedException(
                "This operation has not been implemented.");
        }));

        try
        {
            Task.WaitAll(tasks.ToArray());
        }
        catch (AggregateException ae)
        {
            throw ae.Flatten();
        }
    }
}
// The example displays the following output:
//       UnauthorizedAccessException:
//          Access to the path 'C:\Documents and Settings' is denied.
//       ArgumentException:
//          The system root is not a valid path.
//       NotImplementedException:
//          This operation has not been implemented.
Imports System.Collections.Generic
Imports System.IO
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Try
            ExecuteTasks()
        Catch ae As AggregateException
            For Each e In ae.InnerExceptions
                Console.WriteLine("{0}:{2}   {1}", e.GetType().Name, e.Message,
                                  vbCrLf)
            Next
        End Try
    End Sub

    Sub ExecuteTasks()
        ' Assume this is a user-entered String.
        Dim path = "C:\"
        Dim tasks As New List(Of Task)

        tasks.Add(Task.Run(Function()
                               ' This should throw an UnauthorizedAccessException.
                               Return Directory.GetFiles(path, "*.txt",
                                                         SearchOption.AllDirectories)
                           End Function))

        tasks.Add(Task.Run(Function()
                               If path = "C:\" Then
                                   Throw New ArgumentException("The system root is not a valid path.")
                               End If
                               Return {".txt", ".dll", ".exe", ".bin", ".dat"}
                           End Function))

        tasks.Add(Task.Run(Sub()
                               Throw New NotImplementedException("This operation has not been implemented.")
                           End Sub))

        Try
            Task.WaitAll(tasks.ToArray)
        Catch ae As AggregateException
            Throw ae.Flatten()
        End Try
    End Sub
End Module
' The example displays the following output:
'       UnauthorizedAccessException:
'          Access to the path 'C:\Documents and Settings' is denied.
'       ArgumentException:
'          The system root is not a valid path.
'       NotImplementedException:
'          This operation has not been implemented.

Wyjątki od odłączonych zadań podrzędnych

Domyślnie zadania podrzędne są tworzone jako odłączone. Wyjątki zgłaszane z zadań odłączonych muszą być obsługiwane lub ponownie w bezpośrednim zadaniu nadrzędnym; nie są propagowane z powrotem do wątku wywołującego w taki sam sposób, jak dołączone zadania podrzędne propagowane z powrotem. Najbardziej górny element nadrzędny może ręcznie ponownie wywołać wyjątek od odłączonego elementu podrzędnego, aby spowodować jego zawijanie i AggregateException propagację z powrotem do wątku wywołującego.


public static partial class Program
{
    public static void DetachedTwo()
    {
        var task = Task.Run(() =>
        {
            var nestedTask = Task.Run(
                () => throw new CustomException("Detached child task faulted."));

            // Here the exception will be escalated back to the calling thread.
            // We could use try/catch here to prevent that.
            nestedTask.Wait();
        });

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            foreach (var e in ae.Flatten().InnerExceptions)
            {
                if (e is CustomException)
                {
                    Console.WriteLine(e.Message);
                }
            }
        }
    }
}
// The example displays the following output:
//    Detached child task faulted.
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Run(Sub()
                                 Dim nestedTask1 = Task.Run(Sub()
                                                                Throw New CustomException("Detached child task faulted.")
                                                            End Sub)
                                 ' Here the exception will be escalated back to joining thread.
                                 ' We could use try/catch here to prevent that.
                                 nestedTask1.Wait()
                             End Sub)

        Try
            task1.Wait()
        Catch ae As AggregateException
            For Each ex In ae.Flatten().InnerExceptions
                If TypeOf ex Is CustomException Then
                    ' Recover from the exception. Here we just
                    ' print the message for demonstration purposes.
                    Console.WriteLine(ex.Message)
                End If
            Next
        End Try
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays the following output:
'       Detached child task faulted.

Nawet jeśli używasz kontynuacji do obserwowania wyjątku w zadaniu podrzędnym, wyjątek nadal musi być obserwowany przez zadanie nadrzędne.

Wyjątki wskazujące anulowanie współpracy

Gdy kod użytkownika w zadaniu odpowiada na żądanie anulowania, prawidłową procedurą OperationCanceledException jest zgłoszenie przekazania tokenu anulowania, na którym zostało przekazane żądanie. Przed próbą propagacji wyjątku wystąpienie zadania porównuje token w wyjątku z tym, który został przekazany do niego podczas jego tworzenia. Jeśli są one takie same, zadanie propaguje TaskCanceledException zawinięte w AggregateExceptionobiekcie i można go zobaczyć, gdy zostaną zbadane wyjątki wewnętrzne. Jeśli jednak wątek wywołujący nie czeka na zadanie, ten konkretny wyjątek nie zostanie rozpropagowany. Aby uzyskać więcej informacji, zobacz Anulowanie zadania.

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var task = Task.Factory.StartNew(() =>
{
    CancellationToken ct = token;
    while (someCondition)
    {
        // Do some work...
        Thread.SpinWait(50_000);
        ct.ThrowIfCancellationRequested();
    }
},
token);

// No waiting required.
tokenSource.Dispose();
Dim someCondition As Boolean = True
Dim tokenSource = New CancellationTokenSource()
Dim token = tokenSource.Token

Dim task1 = Task.Factory.StartNew(Sub()
                                      Dim ct As CancellationToken = token
                                      While someCondition = True
                                          ' Do some work...
                                          Thread.SpinWait(500000)
                                          ct.ThrowIfCancellationRequested()
                                      End While
                                  End Sub,
                                  token)

Używanie metody handle do filtrowania wyjątków wewnętrznych

Możesz użyć AggregateException.Handle metody , aby odfiltrować wyjątki, które można traktować jako "obsługiwane" bez użycia dalszej logiki. W delegatu użytkownika dostarczonego AggregateException.Handle(Func<Exception,Boolean>) do metody można sprawdzić typ wyjątku, jego Message właściwość lub inne informacje, które pozwolą określić, czy jest łagodny. Wszelkie wyjątki, dla których zwraca false delegat, są ponownie przywracane w nowym AggregateException wystąpieniu AggregateException.Handle natychmiast po powrocie metody.

Poniższy przykład jest funkcjonalnie odpowiednikiem pierwszego przykładu AggregateException.InnerExceptions w tym temacie, który analizuje każdy wyjątek w kolekcji. Zamiast tego ta procedura obsługi wyjątków wywołuje AggregateException.Handle obiekt metody dla każdego wyjątku i powoduje ponowne wywołanie wyjątków, które nie CustomException są wystąpieniami.


public static partial class Program
{
    public static void HandleMethodThree()
    {
        var task = Task.Run(
            () => throw new CustomException("This exception is expected!"));

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            // Call the Handle method to handle the custom exception,
            // otherwise rethrow the exception.
            ae.Handle(ex =>
            {
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                }
                return ex is CustomException;
            });
        }
    }
}
// The example displays the following output:
//        This exception is expected!
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))

        Try
            task1.Wait()
        Catch ae As AggregateException
            ' Call the Handle method to handle the custom exception,
            ' otherwise rethrow the exception.
            ae.Handle(Function(e)
                          If TypeOf e Is CustomException Then
                              Console.WriteLine(e.Message)
                          End If
                          Return TypeOf e Is CustomException
                      End Function)
        End Try
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays the following output:
'       This exception is expected!

Poniżej przedstawiono bardziej kompletny przykład, który używa AggregateException.Handle metody w celu zapewnienia specjalnej UnauthorizedAccessException obsługi wyjątku podczas wyliczania plików.

public static partial class Program
{
    public static void TaskException()
    {
        // This should throw an UnauthorizedAccessException.
        try
        {
            if (GetAllFiles(@"C:\") is { Length: > 0 } files)
            {
                foreach (var file in files)
                {
                    Console.WriteLine(file);
                }
            }
        }
        catch (AggregateException ae)
        {
            foreach (var ex in ae.InnerExceptions)
            {
                Console.WriteLine(
                    "{0}: {1}", ex.GetType().Name, ex.Message);
            }
        }
        Console.WriteLine();

        // This should throw an ArgumentException.
        try
        {
            foreach (var s in GetAllFiles(""))
            {
                Console.WriteLine(s);
            }
        }
        catch (AggregateException ae)
        {
            foreach (var ex in ae.InnerExceptions)
                Console.WriteLine(
                    "{0}: {1}", ex.GetType().Name, ex.Message);
        }
    }

    static string[] GetAllFiles(string path)
    {
        var task1 =
            Task.Run(() => Directory.GetFiles(
                path, "*.txt",
                SearchOption.AllDirectories));

        try
        {
            return task1.Result;
        }
        catch (AggregateException ae)
        {
            ae.Handle(x =>
            {
                // Handle an UnauthorizedAccessException
                if (x is UnauthorizedAccessException)
                {
                    Console.WriteLine(
                        "You do not have permission to access all folders in this path.");
                    Console.WriteLine(
                        "See your network administrator or try another path.");
                }
                return x is UnauthorizedAccessException;
            });
            return Array.Empty<string>();
        }
    }
}
// The example displays the following output:
//       You do not have permission to access all folders in this path.
//       See your network administrator or try another path.
//
//       ArgumentException: The path is not of a legal form.
Imports System.IO
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        ' This should throw an UnauthorizedAccessException.
        Try
            Dim files = GetAllFiles("C:\")
            If files IsNot Nothing Then
                For Each file In files
                    Console.WriteLine(file)
                Next
            End If
        Catch ae As AggregateException
            For Each ex In ae.InnerExceptions
                Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message)
            Next
        End Try
        Console.WriteLine()

        ' This should throw an ArgumentException.
        Try
            For Each s In GetAllFiles("")
                Console.WriteLine(s)
            Next
        Catch ae As AggregateException
            For Each ex In ae.InnerExceptions
                Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message)
            Next
        End Try
        Console.WriteLine()
    End Sub

    Function GetAllFiles(ByVal path As String) As String()
        Dim task1 = Task.Run(Function()
                                 Return Directory.GetFiles(path, "*.txt",
                                                           SearchOption.AllDirectories)
                             End Function)
        Try
            Return task1.Result
        Catch ae As AggregateException
            ae.Handle(Function(x)
                          ' Handle an UnauthorizedAccessException
                          If TypeOf x Is UnauthorizedAccessException Then
                              Console.WriteLine("You do not have permission to access all folders in this path.")
                              Console.WriteLine("See your network administrator or try another path.")
                          End If
                          Return TypeOf x Is UnauthorizedAccessException
                      End Function)
        End Try
        Return Array.Empty(Of String)()
    End Function
End Module
' The example displays the following output:
'       You do not have permission to access all folders in this path.
'       See your network administrator or try another path.
'
'       ArgumentException: The path is not of a legal form.

Obserwowanie wyjątków przy użyciu właściwości Task.Exception

Jeśli zadanie zostanie ukończone w TaskStatus.Faulted stanie, jego właściwość można zbadać, Exception aby sprawdzić, który wyjątek spowodował błąd. Dobrym sposobem obserwowania Exception właściwości jest użycie kontynuacji uruchamianej tylko wtedy, gdy błędy zadania przedzibowego, jak pokazano w poniższym przykładzie.


public static partial class Program
{
    public static void ExceptionPropagationTwo()
    {
        _ = Task.Run(
            () => throw new CustomException("task1 faulted."))
            .ContinueWith(_ =>
            {
                if (_.Exception?.InnerException is { } inner)
                {
                    Console.WriteLine("{0}: {1}",
                        inner.GetType().Name,
                        inner.Message);
                }
            }, 
            TaskContinuationOptions.OnlyOnFaulted);
        
        Thread.Sleep(500);
    }
}
// The example displays output like the following:
//        CustomException: task1 faulted.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Factory.StartNew(Sub()
                                              Throw New CustomException("task1 faulted.")
                                          End Sub).
                    ContinueWith(Sub(t)
                                     Console.WriteLine("{0}: {1}",
                                                     t.Exception.InnerException.GetType().Name,
                                                     t.Exception.InnerException.Message)
                                 End Sub, TaskContinuationOptions.OnlyOnFaulted)

        Thread.Sleep(500)
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays output like the following:
'       CustomException: task1 faulted.

W znaczącej aplikacji delegat kontynuacji może rejestrować szczegółowe informacje o wyjątku i ewentualnie duplikować nowe zadania w celu odzyskania od wyjątku. Jeśli błąd zadania, następujące wyrażenia zgłaszają wyjątek:

  • await task
  • task.Wait()
  • task.Result
  • task.GetAwaiter().GetResult()

try-catch Użyj instrukcji do obsługi wyjątków zgłaszanych i obserwowania ich. Alternatywnie zwróć uwagę na wyjątek, korzystając Task.Exception z właściwości .

Ważne

Nie AggregateException można jawnie przechwycić elementu w przypadku używania następujących wyrażeń:

  • await task
  • task.GetAwaiter().GetResult()

Zdarzenie UnobservedTaskException

W niektórych scenariuszach, takich jak w przypadku hostowania niezaufanych wtyczek, łagodne wyjątki mogą być powszechne i może być zbyt trudne do ręcznego obserwowania ich wszystkich. W takich przypadkach można obsłużyć TaskScheduler.UnobservedTaskException to zdarzenie. Wystąpienie System.Threading.Tasks.UnobservedTaskExceptionEventArgs przekazane do programu obsługi może służyć do zapobiegania propagacji nieobserwowanego wyjątku z powrotem do wątku przyłączania.

Zobacz też