Verwalten asynchroner und paralleler Aufgaben

Abgeschlossen

Für C#-Entwickler bietet die Task Parallel Library (TPL) eine einfachere Möglichkeit, parallelen Code zu schreiben. Nicht der gesamte Code eignet sich jedoch für die Parallelisierung. Wenn eine Schleife z. B. nur eine kleine Menge Arbeit für jede Iteration ausführt oder sie nicht für viele Iterationen ausgeführt wird, kann der Mehraufwand der Parallelisierung dazu führen, dass der Code langsamer ausgeführt wird. Darüber hinaus fügt die Parallelisierung, wie jeder Multithread-Code, ihrer Programmausführung Komplexität hinzu.

Häufige Fallstricke in Daten und Aufgabenparallelität

In vielen Fällen können Parallel.For und Parallel.ForEach erhebliche Leistungsverbesserungen gegenüber normalen sequenziellen Schleifen erzielen. Die Parallelisierung der Schleife führt jedoch zu Komplexität, die zu Problemen führen kann, die in sequenziellem Code nicht so häufig sind.

Gehen Sie nicht davon aus, dass parallel immer schneller ist

In bestimmten Fällen kann eine parallele Schleife langsamer als die sequenzielle Entsprechung ausgeführt werden. Die Grundlegende Faustregel besteht darin, dass parallele Schleifen mit wenigen Iterationen und schnellen Benutzerdelegatten wahrscheinlich viel schneller werden. Da jedoch viele Faktoren an der Leistung beteiligt sind, empfehlen wir, immer die tatsächlichen Ergebnisse zu messen.

Vermeiden des Schreibens an freigegebene Speicherorte

Im sequenziellen Code ist es nicht ungewöhnlich, aus statischen Variablen oder Klassenfeldern zu lesen oder zu schreiben. Wenn jedoch mehrere Threads gleichzeitig auf solche Variablen zugreifen, gibt es ein erhebliches Potenzial für Rennbedingungen. Obwohl Sie Sperren verwenden können, um den Zugriff auf die Variable zu synchronisieren, kann die Synchronisierungskosten die Leistung beeinträchtigen. Daher wird empfohlen, den Zugriff auf den freigegebenen Zustand in einer parallelen Schleife so weit wie möglich zu vermeiden oder zumindest einzuschränken. Am besten verwenden Sie dazu die Überladungen von Parallel.For und Parallel.ForEach, die eine System.Threading.ThreadLocal<T> Variable zum Speichern des threadlokalen Zustands während der Schleifenausführung verwenden.

Vermeiden von zu starker Parallelisierung

Durch die Verwendung paralleler Schleifen entstehen die Kosten für die Partitionierung der Quellauflistung und die Synchronisierung der Arbeitsthreads. Die Vorteile der Parallelisierung werden durch die Anzahl der Prozessoren auf dem Computer weiter eingeschränkt. Es gibt keinen Geschwindigkeitszuwachs, wenn mehrere rechenintensive Threads auf nur einem Prozessor ausgeführt werden. Daher müssen Sie darauf achten, eine Schleife nicht zu parallelisieren.

Das häufigste Szenario, in dem die übermäßige Parallelisierung auftreten kann, ist in geschachtelten Schleifen. In den meisten Fällen ist es am besten, nur die äußere Schleife zu parallelisieren, es sei denn, eine oder mehrere der folgenden Bedingungen gelten:

  • Die innere Schleife ist als lang bekannt.
  • Sie führen eine teure Berechnung für jede Bestellung durch.
  • Es ist bekannt, dass das Zielsystem über genügend Prozessoren verfügt, um die Anzahl der durch die Parallelisierung der Verarbeitung erzeugten Threads zu bewältigen.

In allen Fällen besteht die beste Möglichkeit, das optimale Abfrage-Shape zu ermitteln, darin, zu testen und zu messen.

Ausnahmebehandlung in asynchronen und parallelen Aufgaben

Wenn Sie die Task Parallel Library (TPL) zum Ausführen von Aufgaben verwenden, können Ausnahmen auf verschiedene Arten auftreten. Am häufigsten tritt es auf, wenn eine Aufgabe eine Ausnahme auslöst. Das Auslösen einer Ausnahme kann auftreten, wenn die Aufgabe in einem Threadpoolthread ausgeführt wird oder wenn sie im Hauptthread ausgeführt wird. In beiden Fällen wird die Ausnahme wieder an den aufrufenden Thread weitergegeben.

Wenn Sie die Task.Wait Methode verwenden, um auf den Abschluss einer Aufgabe zu warten, werden alle Ausnahmen, die von der Aufgabe ausgelöst wurden, an den aufrufenden Thread weitergegeben. Sie können diese Ausnahmen mit einem Try/Catch-Block behandeln. Wenn eine Aufgabe das übergeordnete Element angefügter untergeordneter Aufgaben ist oder wenn Sie auf mehrere Aufgaben warten, können mehrere Ausnahmen ausgelöst werden. Wenn eine oder mehrere Ausnahmen ausgelöst werden, werden sie in eine AggregateException Instanz eingeschlossen.

Die AggregateException-Ausnahme verfügt über eine InnerExceptions-Eigenschaft, die aufgelistet werden kann, um alle ursprünglich ausgelösten Ausnahmen zu analysieren und jede Ausnahme einzeln zu behandeln (bzw. nicht zu behandeln).

Das folgende Beispiel veranschaulicht, wie Ausnahmen behandelt werden, die von einer Aufgabe ausgelöst werden.


public static partial class Program
{
    public static void Main()
    {
        HandleThree();
    }
    
    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;
                }
            }
        }
    }
}

// Define the CustomException class
public class CustomException : Exception
{
    public CustomException(string message) : base(message) { }
}
// The example displays the following output:
//        This exception is expected!

In diesem Beispiel erstellt die HandleThree Methode eine Task, die eine CustomException wirft. Der try/catch-Block fängt die AggregateException-Sammlung ab und durchläuft die InnerExceptions-Sammlung. Wenn die Ausnahme vom Typ CustomExceptionist, wird die Nachricht in der Konsole gedruckt. Wenn es sich um einen anderen Ausnahmetyp handelt, wird sie erneut ausgelöst.

Sie können die ursprünglichen Ausnahmen auch mithilfe der AggregateException.Handle Methode behandeln. Diese Methode verwendet einen Delegaten, der für jede Ausnahme in der InnerExceptions-Auflistung aufgerufen wird. Wenn der Delegat WAHR zurückgibt, wird die Ausnahme als behandelt betrachtet und aus der Auflistung entfernt. Wenn FALSCH zurückgegeben wird, wird die Ausnahme erneut ausgelöst.

Im folgenden Beispiel wird veranschaulicht, wie die Handle Methode zum Behandeln von Ausnahmen verwendet wird, die von einer Aufgabe ausgelöst werden.


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

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            ae.Handle(ex =>
            {
                // Handle the custom exception.
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                    return true;
                }
                // Rethrow any other exception.
                return false;
            });
        }
    }
}

In diesem Beispiel erstellt die HandleFour Methode eine Task, die eine CustomException wirft. Der try/catch-Block fängt AggregateException ab und ruft die Handle-Methode auf. Die Stellvertretung überprüft, ob die Ausnahme vom Typ CustomExceptionist. Wenn die Ausnahme vom Typ CustomException ist, gibt der Delegat die Nachricht auf der Konsole aus und gibt true zurück. Eine Antwort von true zeigt an, dass die Ausnahme behandelt wurde. Wenn es sich bei der Ausnahme um einen anderen Ausnahmetyp handelt, gibt der Delegat false zurück, wodurch die Ausnahme erneut ausgelöst wird.

Zusammenfassung

In dieser Lektion werden Situationen beschrieben, in denen Code nicht für die Parallelisierung geeignet ist und häufige Fallstricke in Daten und Aufgabenparallelität erläutert. Angenommen, parallel ist immer schneller, schreiben Sie an freigegebene Speicherspeicherorte und über parallelisierung. Der Inhalt erläutert außerdem, wie Ausnahmen in asynchronen und parallelen Aufgaben behandelt werden, einschließlich der Verwendung der Task.Wait Methode und der AggregateException.Handle Methode.

Wichtige Punkte

  • Nicht der gesamte Code eignet sich für die Parallelisierung. Das Testen und Messen der Leistung ist wichtig, bevor Code parallelisiert wird.
  • Häufige Fallstricke in Daten und Aufgabenparallelität umfassen die Annahme, dass parallel immer schneller ist, das Schreiben an gemeinsam genutzte Speicherspeicherorte und die Über parallelisierung.
  • Ausnahmen in asynchronen und parallelen Aufgaben können mithilfe der Task.Wait Methode und der AggregateException.Handle Methode behandelt werden.
  • Die AggregateException Ausnahme weist eine InnerExceptions Eigenschaft auf, die aufgezählt werden kann, um alle ursprünglichen Ausnahmen zu untersuchen, die ausgelöst wurden.