Potenzielle Fehler bei Daten- und AufgabenparallelitätPotential Pitfalls in Data and Task Parallelism

In vielen Fällen können Parallel.For und Parallel.ForEach erhebliche Leistungssteigerungen gegenüber gewöhnlichen sequenziellen Schleifen bieten.In many cases, Parallel.For and Parallel.ForEach can provide significant performance improvements over ordinary sequential loops. Die Parallelisierung der Schleife erhöht jedoch die Komplexität des Vorgangs, was Probleme nach sich ziehen kann, die in sequenziellem Code weniger häufig oder gar nicht vorkommen.However, the work of parallelizing the loop introduces complexity that can lead to problems that, in sequential code, are not as common or are not encountered at all. In diesem Thema sind bestimmte Fehlerquellen aufgeführt, die beim Schreiben von parallelen Schleifen vermieden werden sollten.This topic lists some practices to avoid when you write parallel loops.

Gehen Sie nicht davon aus, dass eine parallele Ausführung immer schneller ist.Do Not Assume That Parallel Is Always Faster

In bestimmten Fällen kann eine parallele Schleife langsamer als deren sequenzielle Entsprechung ausgeführt werden.In certain cases a parallel loop might run slower than its sequential equivalent. Eine Faustregel besagt, dass die Geschwindigkeit von parallelen Schleifen mit wenigen Iterationen und schnellen Benutzerdelegaten wahrscheinlich kaum zunimmt.The basic rule of thumb is that parallel loops that have few iterations and fast user delegates are unlikely to speedup much. Da jedoch viele Faktoren Einfluss auf die Leistung haben, wird empfohlen, immer die tatsächlichen Ergebnisse zu messen.However, because many factors are involved in performance, we recommend that you always measure actual results.

Vermeiden Sie es, in gemeinsam genutzte Speicherpositionen zu schreiben.Avoid Writing to Shared Memory Locations

Bei sequenziellem Code wird regelmäßig aus statischen Variablen oder Klassenfeldern gelesen bzw. in diese geschrieben.In sequential code, it is not uncommon to read from or write to static variables or class fields. Wenn jedoch mehrere Threads gleichzeitig auf diese Variablen zugreifen, besteht eine hohe Wahrscheinlichkeit für Racebedingungen.However, whenever multiple threads are accessing such variables concurrently, there is a big potential for race conditions. Sie können den Zugriff auf die Variable mithilfe von Sperren zwar synchronisieren, die Synchronisierung geht jedoch zu Lasten der Leistung.Even though you can use locks to synchronize access to the variable, the cost of synchronization can hurt performance. Es empfiehlt sich daher, den Zugriff auf den Freigabezustand in einer parallelen Schleife zu vermeiden oder so weit wie möglich einzuschränken.Therefore, we recommend that you avoid, or at least limit, access to shared state in a parallel loop as much as possible. Dies geschieht am besten durch Verwendung der Ü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.The best way to do this is to use the overloads of Parallel.For and Parallel.ForEach that use a System.Threading.ThreadLocal<T> variable to store thread-local state during loop execution. Weitere Informationen finden Sie unter Vorgehensweise: Schreiben einer Parallel.For-Schleife mit threadlokalen Variablen und Vorgehensweise: Schreiben einer Parallel.ForEach-Schleife mit partitionslokalen Variablen.For more information, see How to: Write a Parallel.For Loop with Thread-Local Variables and How to: Write a Parallel.ForEach Loop with Partition-Local Variables.

Vermeiden Sie eine zu starke Parallelisierung.Avoid Over-Parallelization

Mithilfe von parallelen Schleifen übernehmen Sie die Mehrkosten für das Partitionieren der Quellauflistung und das Synchronisieren der Arbeitsthreads.By using parallel loops, you incur the overhead costs of partitioning the source collection and synchronizing the worker threads. Die Vorteile der Parallelisierung werden zudem durch die Anzahl der Prozessoren auf dem Computer beschränkt.The benefits of parallelization are further limited by the number of processors on the computer. Die Ausführung von mehreren rechnergebundenen Threads auf nur einem Prozessor ermöglicht keine Geschwindigkeitssteigerung.There is no speedup to be gained by running multiple compute-bound threads on just one processor. Achten Sie daher darauf, dass Sie eine Schleife nicht zu stark parallelisieren.Therefore, you must be careful not to over-parallelize a loop.

Eine zu starke Parallelisierung tritt vor allem in geschachtelten Schleifen auf, wie im folgenden Ausschnitt gezeigt wird.The most common scenario in which over-parallelization can occur is in nested loops. In den meisten Fällen sollte idealerweise nur die äußere Schleife parallelisiert werden, sofern nicht eine oder mehrere der folgenden Bedingungen erfüllt sind:In most cases, it is best to parallelize only the outer loop unless one or more of the following conditions apply:

  • Die innere Schleife ist bekanntermaßen sehr lang.The inner loop is known to be very long.

  • Sie führen für jede Bestellung eine umfangreiche Berechnung aus.You are performing an expensive computation on each order. (Der im Beispiel gezeigte Vorgang ist nicht sehr rechenintensiv.)(The operation shown in the example is not expensive.)

  • Das Zielsystem verfügt über genug Prozessoren für die Verarbeitung der Anzahl von Threads, die durch die Parallelisierung der Abfrage von cust.Orders erzeugt werden.The target system is known to have enough processors to handle the number of threads that will be produced by parallelizing the query on cust.Orders.

In allen diesen Fällen empfiehlt es sich, die optimale Abfrageform mithilfe von Tests und Messungen zu ermitteln.In all cases, the best way to determine the optimum query shape is to test and measure.

Vermeiden Sie den Aufruf nicht threadsicherer Methoden.Avoid Calls to Non-Thread-Safe Methods

Das Schreiben in nicht threadsichere Instanzmethoden von einer parallelen Schleife aus kann zu Datenbeschädigungen führen, die im Programm möglicherweise unerkannt bleiben.Writing to non-thread-safe instance methods from a parallel loop can lead to data corruption which may or may not go undetected in your program. Dies kann auch zu Ausnahmen führen.It can also lead to exceptions. Im folgenden Beispiel würden mehrere Threads gleichzeitig versuchen, die FileStream.WriteByte-Methode aufzurufen, was von der Klasse nicht unterstützt wird.In the following example, multiple threads would be attempting to call the FileStream.WriteByte method simultaneously, which is not supported by the class.

FileStream fs = File.OpenWrite(path);
byte[] bytes = new Byte[10000000];
// ...
Parallel.For(0, bytes.Length, (i) => fs.WriteByte(bytes[i]));
Dim fs As FileStream = File.OpenWrite(filepath)
Dim bytes() As Byte
ReDim bytes(1000000)
' ...init byte array
Parallel.For(0, bytes.Length, Sub(n) fs.WriteByte(bytes(n)))

Beschränken Sie Aufrufe auf threadsichere Methoden.Limit Calls to Thread-Safe Methods

Die meisten statischen Methoden in .NET Framework sind threadsicher und können von mehreren Threads gleichzeitig aufgerufen werden.Most static methods in the .NET Framework are thread-safe and can be called from multiple threads concurrently. Die damit verbundene Synchronisierung kann jedoch auch in diesen Fällen zu einer erheblichen Verlangsamung der Abfrage führen.However, even in these cases, the synchronization involved can lead to significant slowdown in the query.

Hinweis

Sie können dies testen, indem Sie in Ihre Abfragen Aufrufe von WriteLine einfügen.You can test for this yourself by inserting some calls to WriteLine in your queries. Diese Methode wird jedoch nur in den Dokumentationsbeispielen zu Demonstrationszwecken verwendet. Nutzen Sie sie nur in parallelen Schleifen, wenn dies erforderlich ist.Although this method is used in the documentation examples for demonstration purposes, do not use it in parallel loops unless necessary.

Beachten Sie Threadaffinitätsprobleme.Be Aware of Thread Affinity Issues

Einige Technologien, z. B. COM-Interoperabilität für STA-Komponenten (Singlethread-Apartment), Windows Forms und Windows Presentation Foundation (WPF), erzeugen Threadaffinitätseinschränkungen, aufgrund derer Code in einem bestimmten Thread ausgeführt werden muss.Some technologies, for example, COM interoperability for Single-Threaded Apartment (STA) components, Windows Forms, and Windows Presentation Foundation (WPF), impose thread affinity restrictions that require code to run on a specific thread. Beispielsweise kann sowohl in Windows Forms als auch in WPF nur in einem Thread auf ein Steuerelement zugegriffen werden, in dem es erstellt wurde.For example, in both Windows Forms and WPF, a control can only be accessed on the thread on which it was created. Dies bedeutet beispielsweise, dass Sie kein Listensteuerelement von einer parallelen Schleife aktualisieren können, außer wenn Sie den Threadplaner konfigurieren, um die Arbeit nur im UI-Thread zu planen.This means, for example, that you cannot update a list control from a parallel loop unless you configure the thread scheduler to schedule work only on the UI thread. Weitere Informationen finden Sie unter Angeben eines Synchronisierungskontexts.For more information, see Specifying a synchronization context.

Seien Sie vorsichtig, wenn Sie in Delegaten warten, die von Parallel.Invoke aufgerufen werden.Use Caution When Waiting in Delegates That Are Called by Parallel.Invoke

Unter bestimmten Umständen wird ein Task von der Task Parallel Library inline ausgeführt, d.h. die Ausführung erfolgt im derzeit ausgeführten Thread.In certain circumstances, the Task Parallel Library will inline a task, which means it runs on the task on the currently executing thread. (Weitere Informationen finden Sie unter TaskScheduler-Klasse.) Diese Leistungsoptimierung kann in bestimmten Fällen einen Deadlock zur Folge haben.(For more information, see Task Schedulers.) This performance optimization can lead to deadlock in certain cases. Beispiel: Bei zwei Tasks wird möglicherweise der gleiche Delegatcode ausgeführt, der signalisiert, wenn ein Ereignis auftritt und anschließend auf die Signalisierung des anderen Tasks wartet.For example, two tasks might run the same delegate code, which signals when an event occurs, and then waits for the other task to signal. Wenn der zweite Task im gleichen Thread wie der erste Task inline ausgeführt wird und der erste Task in einen Wartezustand versetzt wird, kann der zweite Task das Ereignis niemals signalisieren.If the second task is inlined on the same thread as the first, and the first goes into a Wait state, the second task will never be able to signal its event. Um dies zu vermeiden, können Sie für den Wartevorgang ein Timeout angeben oder explizite Threadkonstruktoren verwenden, um sicherzustellen, dass ein Task nicht den anderen blockieren kann.To avoid such an occurrence, you can specify a timeout on the Wait operation, or use explicit thread constructors to help ensure that one task cannot block the other.

Gehen Sie nicht davon aus, dass Iterationen von „ForEach“, „For“ und „ForAll“ immer parallel ausgeführt werden.Do Not Assume that Iterations of ForEach, For and ForAll Always Execute in Parallel

Beachten Sie unbedingt, dass einzelne Iterationen in einer For-, ForEach- oder ForAll-Schleife parallel ausgeführt werden können, jedoch nicht parallel ausgeführt werden müssen.It is important to keep in mind that individual iterations in a For, ForEach or ForAll loop may but do not have to execute in parallel. Schreiben Sie daher nach Möglichkeit keinen Code, dessen Korrektheit von der parallelen Ausführung von Iterationen oder der Ausführung von Iterationen in einer bestimmten Reihenfolge abhängig ist.Therefore, you should avoid writing any code that depends for correctness on parallel execution of iterations or on the execution of iterations in any particular order. Beim folgenden Code ist z. B. ein Deadlock wahrscheinlich:For example, this code is likely to deadlock:

ManualResetEventSlim mre = new ManualResetEventSlim();
Enumerable.Range(0, Environment.ProcessorCount * 100)
    .AsParallel()
    .ForAll((j) =>
        {
            if (j == Environment.ProcessorCount)
            {
                Console.WriteLine("Set on {0} with value of {1}",
                    Thread.CurrentThread.ManagedThreadId, j);
                mre.Set();
            }
            else
            {
                Console.WriteLine("Waiting on {0} with value of {1}",
                    Thread.CurrentThread.ManagedThreadId, j);
                mre.Wait();
            }
        }); //deadlocks
Dim mres = New ManualResetEventSlim()
Enumerable.Range(0, Environment.ProcessorCount * 100) _
.AsParallel() _
.ForAll(Sub(j)

            If j = Environment.ProcessorCount Then
                Console.WriteLine("Set on {0} with value of {1}",
                                  Thread.CurrentThread.ManagedThreadId, j)
                mres.Set()
            Else
                Console.WriteLine("Waiting on {0} with value of {1}",
                                  Thread.CurrentThread.ManagedThreadId, j)
                mres.Wait()
            End If
        End Sub) ' deadlocks

In diesem Beispiel wird durch eine Iteration ein Ereignis festgelegt, und alle anderen Iterationen warten auf das Ereignis.In this example, one iteration sets an event, and all other iterations wait on the event. Die wartenden Iterationen können erst nach Abschluss der ereignisauslösenden Iteration abgeschlossen werden.None of the waiting iterations can complete until the event-setting iteration has completed. Es ist jedoch möglich, dass die wartenden Iterationen alle Threads blockieren, die zur Ausführung der parallelen Schleife verwendet werden, bevor die ereignisauslösende Iteration überhaupt ausgeführt werden kann.However, it is possible that the waiting iterations block all threads that are used to execute the parallel loop, before the event-setting iteration has had a chance to execute. Dies führt zu einem Deadlock. Die ereignisauslösende Iteration wird niemals ausgeführt, und die wartenden Iterationen werden zu keinem Zeitpunkt aktiviert.This results in a deadlock – the event-setting iteration will never execute, and the waiting iterations will never wake up.

Insbesondere sollte eine Iteration einer parallelen Schleife nie auf den Fortschritt einer anderen Iteration der Schleife warten.In particular, one iteration of a parallel loop should never wait on another iteration of the loop to make progress. Wenn von der parallelen Schleife entschieden wird, die Iterationen sequenziell zu planen, jedoch in der entgegengesetzten Reihenfolge, tritt ein Deadlock auf.If the parallel loop decides to schedule the iterations sequentially but in the opposite order, a deadlock will occur.

Vermeiden Sie die Ausführung paralleler Schleifen im UI-Thread.Avoid Executing Parallel Loops on the UI Thread

Es ist wichtig, die Reaktionsfähigkeit der Benutzeroberfläche der Anwendung zu erhalten.It is important to keep your application's user interface (UI) responsive. Wenn ein Vorgang genug Arbeit enthält, um Parallelisierung zu garantieren, darf der Vorgang wahrscheinlich nicht im UI-Thread ausgeführt werden.If an operation contains enough work to warrant parallelization, then it likely should not be run that operation on the UI thread. Stattdessen sollte der Vorgang abgeladen werden, um eine Ausführung als Hintergrundthread zu ermöglichen.Instead, it should offload that operation to be run on a background thread. Wenn Sie z.B. eine parallele Schleife verwenden möchten, um einige Daten zu berechnen, die dann in ein UI-Steuerelement gerendert werden sollen, führen Sie die Schleife ggf. innerhalb einer Aufgabeninstanz und nicht direkt in einem UI-Ereignishandler aus.For example, if you want to use a parallel loop to compute some data that should then be rendered into a UI control, you should consider executing the loop within a task instance rather than directly in a UI event handler. Erst nach der Kernberechnung können Sie die Aktualisierung der Benutzeroberfläche zurück zum UI-Thread marshallen.Only when the core computation has completed should you then marshal the UI update back to the UI thread.

Wenn Sie parallele Schleifen im UI-Thread ausführen, aktualisieren Sie innerhalb der Schleife keine Benutzeroberflächenelemente.If you do run parallel loops on the UI thread, be careful to avoid updating UI controls from within the loop. Der Versuch, UI-Steuerelemente innerhalb einer parallelen Schleife zu aktualisieren, die im UI-Thread ausgeführt wird, kann zu Zustandsbeschädigung, Ausnahmen, verzögerten Updates und sogar Deadlocks führen, und zwar abhängig davon, wie das Update der Benutzeroberfläche aufgerufen wird.Attempting to update UI controls from within a parallel loop that is executing on the UI thread can lead to state corruption, exceptions, delayed updates, and even deadlocks, depending on how the UI update is invoked. Im folgenden Beispiel blockiert die parallele Schleife den UI-Thread, in dem sie ausgeführt wird, bis alle Iterationen abgeschlossen wurden.In the following example, the parallel loop blocks the UI thread on which it’s executing until all iterations are complete. Wenn eine Iteration der Schleife jedoch in einem Hintergrundthread ausgeführt wird (möglicherweise wie bei For), verursacht der Aufruf von „Invoke“ die Übermittlung einer Meldung an den UI-Thread, und er blockiert das Warten auf die Verarbeitung der Nachricht.However, if an iteration of the loop is running on a background thread (as For may do), the call to Invoke causes a message to be submitted to the UI thread and blocks waiting for that message to be processed. Da der UI-Thread, in dem For ausgeführt wird, blockiert ist, kann die Nachricht nie verarbeitet werden, und im UI-Thread kommt es zu einem Deadlock.Since the UI thread is blocked running the For, the message can never be processed, and the UI thread deadlocks.

private void button1_Click(object sender, EventArgs e)
{
    Parallel.For(0, N, i =>
    {
        // do work for i
        button1.Invoke((Action)delegate { DisplayProgress(i); });
    });
}
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim iterations As Integer = 20
    Parallel.For(0, iterations, Sub(x)
                                    Button1.Invoke(Sub()
                                                       DisplayProgress(x)
                                                   End Sub)
                                End Sub)
End Sub

Im folgenden Beispiel wird gezeigt, wie der Deadlock vermieden wird, indem die Schleife in einer Aufgabeninstanz ausgeführt wird.The following example shows how to avoid the deadlock, by running the loop inside a task instance. Der UI-Thread wird nicht von der Schleife blockiert, und die Meldung kann verarbeitet werden.The UI thread is not blocked by the loop, and the message can be processed.

private void button1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew(() =>
        Parallel.For(0, N, i =>
        {
            // do work for i
            button1.Invoke((Action)delegate { DisplayProgress(i); });
        })
         );
}
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim iterations As Integer = 20
    Task.Factory.StartNew(Sub() Parallel.For(0, iterations, Sub(x)
                                                                Button1.Invoke(Sub()
                                                                                   DisplayProgress(x)
                                                                               End Sub)
                                                            End Sub))
End Sub

Siehe auchSee also