Megosztás a következőn keresztül:


Kivételkezelés (Feladat párhuzamos tára)

A feladaton belül futó felhasználói kód által elvetett kezeletlen kivételek vissza lesznek propagálása a hívó szálra, kivéve a jelen témakör későbbi részében ismertetett forgatókönyveket. A kivételeket akkor propagálja a rendszer, ha a statikus vagy a példány Task.Wait egyik metódusát használja, és a hívás utasításba try/catch való belefoglalásával kezeli őket. Ha egy tevékenység a csatolt gyermekfeladatok szülője, vagy ha több tevékenységre vár, több kivétel is előfordulhat.

Ha az összes kivételt vissza szeretné propagálja a hívó szálra, a Feladat infrastruktúra egy AggregateException példányba csomagolja őket. A AggregateException kivételnek van egy InnerExceptions tulajdonsága, amely számba vehető az összes eredeti kivétel vizsgálatához, és egyenként kezeli (vagy nem kezeli) őket. Az eredeti kivételeket a metódussal AggregateException.Handle is kezelheti.

Még ha csak egy kivétel is ki van vetve, a rendszer továbbra is kivételbe AggregateException burkolva van, ahogy az alábbi példa is mutatja.


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!

Elkerülheti a kezeletlen kivételeket, ha csak a belső kivételeket észleli AggregateException , és nem figyeli meg. Javasoljuk azonban, hogy ne tegye ezt meg, mert hasonló az alaptípus Exception elfogásához nem párhuzamos forgatókönyvekben. Ha egy kivételt anélkül szeretne elkapni, hogy konkrét műveleteket hajt végre a helyreállításhoz, meghatározhatatlan állapotban hagyhatja a programot.

Ha nem szeretné meghívni a Task.Wait metódust, hogy megvárja egy tevékenység befejezését, a kivételt AggregateException a tevékenység Exception tulajdonságából is lekérheti, ahogy az alábbi példa is mutatja. További információ: A kivételek megfigyelése a témakör Task.Exception tulajdonság szakaszával.


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!

Figyelemfelhívás

Az előző példakód tartalmaz egy hurkot while , amely lekérdezi a tevékenység tulajdonságát Task.IsCompleted annak megállapításához, hogy a tevékenység mikor fejeződött be. Ezt soha nem szabad éles kódban elvégezni, mivel nagyon nem hatékony.

Ha nem várakozik egy kivételt propagáló tevékenységre, vagy nem fér hozzá a tulajdonságához Exception , a kivétel a .NET kivételszabályzat szerint eszkalálódik a tevékenység szemétgyűjtésekor.

Ha a kivételek visszaugranak az összekapcsolt szálra, lehetséges, hogy a kivétel felmerülése után a tevékenységek tovább dolgoznak fel bizonyos elemeket.

Feljegyzés

Ha a "Just My Code" engedélyezve van, a Visual Studio bizonyos esetekben megszakad a kivételt jelző sorban, és megjelenik egy hibaüzenet, amely azt jelzi, hogy "a felhasználói kód által nem kezelt kivétel". Ez a hiba jóindulatú. A folytatáshoz nyomja le az F5 billentyűt, és tekintse meg az ezekben a példákban bemutatott kivételkezelési viselkedést. Ha meg szeretné akadályozni, hogy a Visual Studio feltörje az első hibát, törölje a jelet a Csak saját kód engedélyezése jelölőnégyzetből az Eszközök, Beállítások, Hibakeresés, Általános területen.

Csatolt gyermekfeladatok és beágyazott AggregateExceptions

Ha egy tevékenység rendelkezik egy olyan csatolt gyermektevékenységtel, amely kivételt eredményez, a kivételt a rendszer a AggregateException szülőtevékenységbe való propagálás előtt burkolja, amely a kivételt saját AggregateException maga csomagolja, mielőtt újra propagálja azt a hívószálra. Ilyen esetekben a InnerExceptions kivétel tulajdonsága, amely a Task.WaitAggregateException , , WaitAnyvagy WaitAll metóduson fogott, egy vagy több AggregateException példányt tartalmaz, nem pedig a hibát okozó eredeti kivételeket. A beágyazott AggregateException kivételek iterálásának elkerülése érdekében a Flatten metódus használatával eltávolíthatja az összes beágyazott kivételt AggregateException , hogy a AggregateException.InnerExceptions tulajdonság tartalmazza az eredeti kivételeket. Az alábbi példában a beágyazott AggregateException példányok egyetlen hurokban vannak összeolvadva és kezelve.


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.

A metódussal AggregateException.Flatten újra létrehozhatja a több AggregateException példányból származó belső kivételeket, amelyeket egyetlen AggregateException példányban több feladat dobott ki, ahogyan az alábbi példa is mutatja.

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.

Kivételek a leválasztott gyermekfeladatok alól

Alapértelmezés szerint a gyermekfeladatok leválasztottként jönnek létre. A leválasztott tevékenységekből származó kivételeket az azonnali szülőtevékenységben kell kezelni vagy újra végrehajtani; a rendszer nem propagálja őket a hívó szálra ugyanúgy, mint a csatolt gyermekfeladatok. A legfelső szülő manuálisan újra létrehozhat egy kivételt egy leválasztott gyermektől, hogy az egybe AggregateException burkolódjon, és visszaterjedjen a hívó szálra.


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.

Még akkor is, ha egy gyermekfeladat kivételének megfigyelésére folytatást használ, a kivételt a szülőtevékenységnek továbbra is be kell tartania.

A kooperatív lemondást jelző kivételek

Amikor egy feladat felhasználói kódja válaszol egy lemondási kérelemre, a helyes eljárás az, ha a OperationCanceledException lemondási jogkivonatot adja meg, amelyen a kérést közölték. Mielőtt propagálja a kivételt, a feladatpéldány összehasonlítja a kivétel jogkivonatát azzal, amelyet a létrehozásakor átadtak neki. Ha megegyeznek, a tevékenység egy becsomagolt elemet propagálja TaskCanceledException , AggregateExceptionés a belső kivételek vizsgálatakor látható. Ha azonban a hívó szál nem várakozik a feladatra, a rendszer nem propagálja ezt a kivételt. További információ: Tevékenység lemondása.

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)

Belső kivételek szűrése a leíró metódus használatával

A metódussal kiszűrheti azokat a AggregateException.Handle kivételeket, amelyeket "kezeltként" kezelhet további logika nélkül. A metódushoz AggregateException.Handle(Func<Exception,Boolean>) megadott felhasználói delegáltban megvizsgálhatja a kivétel típusát, tulajdonságát Message vagy bármely egyéb információt, amely alapján megállapíthatja, hogy jóindulatú-e. Azok a kivételek, amelyek esetében a delegált visszatérfalse, a metódus visszatérése után AggregateException.Handle azonnal új példányba AggregateException kerülnek.

Az alábbi példa funkcionálisan egyenértékű a jelen témakör első példájával, amely a gyűjtemény minden kivételét AggregateException.InnerExceptions megvizsgálja. Ez a kivételkezelő ehelyett minden kivételhez meghívja a AggregateException.Handle metódusobjektumot, és csak a nem CustomException példányokat tartalmazó kivételeket iktatja újra.


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!

Az alábbiakban egy teljesebb példát mutatunk be, amely a metódust használja a AggregateException.Handle kivételek speciális kezelésére UnauthorizedAccessException a fájlok számbavételekor.

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.

Kivételek megfigyelése a Task.Exception tulajdonság használatával

Ha egy tevékenység befejeződött az TaskStatus.Faulted állapotban, annak tulajdonsága Exception megvizsgálható, hogy kiderüljön, melyik kivétel okozta a hibát. A tulajdonság megfigyeléséhez Exception érdemes olyan folytatást használni, amely csak akkor fut, ha az előzményfeladat hibás, ahogy az alábbi példában is látható.


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.

Egy értelmes alkalmazásban a folytatási meghatalmazott részletes információkat naplózhat a kivételről, és új feladatokat hozhat létre a kivételből való helyreállításhoz. Ha egy feladat meghibásodik, a következő kifejezések kivételt eredményeznek:

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

Utasítással try-catch kezelheti és megfigyelheti a kidobott kivételeket. Másik lehetőségként figyelje meg a kivételt a Task.Exception tulajdonság elérésével.

Fontos

A AggregateException következő kifejezések használatakor nem lehet explicit módon megragadni:

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

UnobservedTaskException esemény

Bizonyos esetekben, például a nem megbízható beépülő modulok használatakor gyakoriak lehetnek a jóindulatú kivételek, és túl nehéz lehet manuálisan megfigyelni őket. Ezekben az esetekben kezelheti az eseményt TaskScheduler.UnobservedTaskException . A System.Threading.Tasks.UnobservedTaskExceptionEventArgs kezelőnek átadott példány használatával megakadályozhatja, hogy a kivételt visszaterjedjen az illesztőszálra.

Lásd még