Obsługa wyjątków (Biblioteka zadań równoległych)

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 w przypadku użycia jednej z metod statycznych lub metod wystąpień, które są obsługiwane przez dołączenie wywołania Task.Wait w instrukcji try / catch . Jeśli zadanie podrzędne 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ń opakowywuje je w AggregateException wystąpieniu. Wyjątek ma właściwość , która może być wyliczana w celu zbadania wszystkich oryginalnych wyjątków, które zostały zgłoszone, i obsługi AggregateException InnerExceptions (lub nie) każdego z nich indywidualnie. Możesz również obsługiwać oryginalne wyjątki przy użyciu AggregateException.Handle metody .

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

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );

      try
      {
          task1.Wait();
      }
      catch (AggregateException ae)
      {
          foreach (var e in ae.InnerExceptions) {
              // Handle the custom exception.
              if (e is CustomException) {
                  Console.WriteLine(e.Message);
              }
              // Rethrow any other exception.
              else {
                  throw e;
              }
          }
      }
   }
}

public class CustomException : Exception
{
   public CustomException(String message) : base(message)
   {}
}
// 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, po prostu przechwytując wyjątek i nie obserwując AggregateException żadnych wyjątków wewnętrznych. Zalecamy jednak, aby tego nie robić, ponieważ jest to analogiczne do przechwytowania typu podstawowego w scenariuszach Exception nie równoległych. Aby przechwyć wyjątek bez podejmowania określonych działań w celu odzyskania go, program może pozostawić go w stanie nieokreślonym.

Jeśli nie chcesz, aby metoda czekała na ukończenie zadania, możesz również pobrać wyjątek z właściwości zadania, jak pokazano w Task.Wait AggregateException poniższym Exception 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.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );

      while(! task1.IsCompleted) {}

      if (task1.Status == TaskStatus.Faulted) {
          foreach (var e in task1.Exception.InnerExceptions) {
              // Handle the custom exception.
              if (e is CustomException) {
                  Console.WriteLine(e.Message);
              }
              // Rethrow any other exception.
              else {
                  throw e;
              }
          }
      }
   }
}

public class CustomException : Exception
{
   public CustomException(String message) : base(message)
   {}
}
// 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!

Jeśli nie czekasz na zadanie, które propaguje wyjątek, lub uzyskujesz dostęp do jego właściwości, wyjątek jest eskalowany zgodnie z zasadami wyjątku .NET, gdy zadanie jest Exception odśmiecane.

Gdy wyjątki mogą bąbelkować z powrotem do wątku przyłączania, istnieje możliwość, że zadanie może kontynuować przetwarzanie niektórych elementów po zwróceniu wyjątku.

Uwaga

Gdy opcja "Tylko mój kod" jest włączona, Visual Studio w niektórych przypadkach spowoduje przerwę w wierszu, który zgłasza wyjątek, i wyświetli komunikat o błędzie z komunikatem "Wyjątek nie jest obsługiwany przez kod użytkownika". Ten błąd jest niegroźny. Możesz nacisnąć klawisz F5, aby kontynuować i zobaczyć zachowanie obsługi wyjątków, które przedstawiono w tych przykładach. Aby zapobiec Visual Studio pierwszego błędu, 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 aggregateExceptions

Jeśli zadanie podrzędne ma dołączone zadanie podrzędne, które zgłasza wyjątek, ten wyjątek jest opakowany w element , zanim zostanie rozpropagowany do zadania nadrzędnego, które opakuje ten wyjątek samodzielnie przed jego propagację do wątku AggregateException AggregateException wywołującego. W takich przypadkach właściwość wyjątku przechwyconego w metodzie , lub zawiera co najmniej jedno wystąpienie, a nie oryginalne wyjątki, InnerExceptions AggregateException które Task.Wait WaitAny WaitAll AggregateException spowodowały błąd. Aby uniknąć konieczności iterowania po zagnieżdżonych wyjątkach, można użyć metody , aby usunąć wszystkie zagnieżdżone wyjątki, tak aby właściwość AggregateException Flatten AggregateException AggregateException.InnerExceptions zawierała oryginalne wyjątki. W poniższym przykładzie AggregateException zagnieżdżone wystąpienia są spłaszczone i obsługiwane tylko w jednej pętli.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var task1 = Task.Factory.StartNew(() => {
                     var child1 = Task.Factory.StartNew(() => {
                        var child2 = 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 {
         task1.Wait();
      }
      catch (AggregateException ae) {
         foreach (var e in ae.Flatten().InnerExceptions) {
            if (e is CustomException) {
               Console.WriteLine(e.Message);
            }
            else {
               throw;
            }
         }
      }
   }
}

public class CustomException : Exception
{
   public CustomException(String message) : base(message)
   {}
}
// 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ć metody , aby ponownie określić wyjątki wewnętrzne z wielu wystąpień zgłaszanych przez wiele zadań w jednym wystąpieniu, jak pokazano w AggregateException.Flatten AggregateException poniższym AggregateException przykładzie.

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
public class Example
{
   public static void Main()
   {
        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 List<Task>();

        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łączone zadania podrzędne

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

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var task1 = Task.Run(() => {
                       var nested1 = 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.
          nested1.Wait();
      });

      try {
         task1.Wait();
      }
      catch (AggregateException ae) {
         foreach (var e in ae.Flatten().InnerExceptions) {
            if (e is CustomException) {
               Console.WriteLine(e.Message);
            }
         }
      }
   }
}

public class CustomException : Exception
{
   public CustomException(String message) : base(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ć zaobserwowany przez zadanie nadrzędne.

Wyjątki, które wskazują na anulowanie kooperatywne

Gdy kod użytkownika w zadaniu odpowiada na żądanie anulowania, prawidłową procedurą jest zgłoszenie przekazywania tokenu anulowania, o którym żądanie OperationCanceledException zostało przekazane. Przed podjęciem próby propagacji wyjątku wystąpienie zadania porównuje token w wyjątku z tym, który został do niego przekazany podczas jego tworzenia. Jeśli są takie same, zadanie propaguje opakowane w , i może być widoczne podczas badania TaskCanceledException AggregateException wyjątków wewnętrznych. 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 task1 = Task.Factory.StartNew(() =>
{
    CancellationToken ct = token;
    while (someCondition)
    {
        // Do some work...
        Thread.SpinWait(50000);
        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ć metody , aby odfiltrować wyjątki, które można traktować jako AggregateException.Handle "obsłużone" bez korzystania z dalszej logiki. W delegowaniu użytkownika dostarczonym do metody można sprawdzić typ wyjątku, jego właściwość lub inne informacje o nim, które pozwolą określić, czy jest AggregateException.Handle(Func<Exception,Boolean>) Message niegroźny. Wszelkie wyjątki, dla których delegat zwraca wartość , są ponownie zwracane w false nowym AggregateException wystąpieniu natychmiast po AggregateException.Handle zwracaniu metody.

Poniższy przykład jest funkcjonalnie odpowiednikiem pierwszego przykładu w tym temacie, który sprawdza każdy wyjątek w AggregateException.InnerExceptions kolekcji. Zamiast tego ten program obsługi wyjątków wywołuje obiekt metody dla każdego wyjątku i ponownie wywołuje tylko wyjątki, AggregateException.Handle które nie CustomException są wystąpieniami.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );

      try {
          task1.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;
                        });
      }
   }
}

public class CustomException : Exception
{
   public CustomException(String message) : base(message)
   {}
}
// 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 metody w celu zapewnienia specjalnej obsługi AggregateException.Handle UnauthorizedAccessException wyjątku podczas wyliczania plików.

using System;
using System.IO;
using System.Threading.Tasks;

public class Example
{
    public static void Main()
    {
        // This should throw an UnauthorizedAccessException.
       try {
           var files = GetAllFiles(@"C:\");
           if (files != null)
              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 zakończone w stanie , można zbadać jego właściwość, aby wykryć, który TaskStatus.Faulted Exception wyjątek spowodował błąd. Dobrym sposobem obserwowania właściwości jest użycie kontynuacji, która jest uruchamiana tylko wtedy, gdy błędy zadania Exception asedent, jak pokazano w poniższym przykładzie.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var task1 = Task.Run(() =>
                           { throw new CustomException("task1 faulted.");
      }).ContinueWith( t => { Console.WriteLine("{0}: {1}",
                                                t.Exception.InnerException.GetType().Name,
                                                t.Exception.InnerException.Message);
                            }, TaskContinuationOptions.OnlyOnFaulted);
      Thread.Sleep(500);
   }
}

public class CustomException : Exception
{
   public CustomException(String message) : base(message)
   {}
}
// 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 prawdopodobnie duplikować nowe zadania do odzyskania po wyjątku. Jeśli zadanie zostanie uszkodzone, następujące wyrażenia zrzucą wyjątek:

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

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

Zdarzenie UnobservedTaskException

W niektórych scenariuszach, na przykład w przypadku hostowania niezaufanych wtyczek, niegroźne wyjątki mogą być typowe i zbyt trudne do ręcznego obserwowania wszystkich. W takich przypadkach można obsłużyć TaskScheduler.UnobservedTaskException zdarzenie. Wystąpienie przekazywane do procedury obsługi może służyć do zapobiegania propagacji niezaobserwowanych wyjątków z powrotem do wątku System.Threading.Tasks.UnobservedTaskExceptionEventArgs dołączania.

Zobacz też