Best Practices für verwaltetes ThreadingManaged threading best practices

Wenn Sie mehrere Threads verwenden, ist eine sorgfältige Programmierung erforderlich.Multithreading requires careful programming. Für die meisten Aufgaben können Sie die Komplexität reduzieren, indem Sie Ausführungsanforderungen mithilfe von Threadpoolthreads in Warteschlangen einfügen.For most tasks, you can reduce complexity by queuing requests for execution by thread pool threads. Dieses Thema behandelt problematische Situationen wie die Koordinierung der Verarbeitung von mehreren Threads oder die Behandlung von blockierenden Threads.This topic addresses more difficult situations, such as coordinating the work of multiple threads, or handling threads that block.

Hinweis

Ab .NET Framework 4 stellen die Task Parallel Library und PLINQ APIs bereit, die die Komplexität und Risiken der Multithreadprogrammierung etwas reduzieren.Starting with the .NET Framework 4, the Task Parallel Library and PLINQ provide APIs that reduce some of the complexity and risks of multi-threaded programming. Weitere Informationen finden Sie unter Parallele Programmierung in .NET.For more information, see Parallel Programming in .NET.

Deadlocks und RacebedingungenDeadlocks and race conditions

Das Multithreading löst Probleme mit dem Durchsatz und der Ansprechempfindlichkeit, verursacht dabei jedoch neue Probleme: Deadlocks und Racebedingungen.Multithreading solves problems with throughput and responsiveness, but in doing so it introduces new problems: deadlocks and race conditions.

DeadlocksDeadlocks

Ein Deadlock liegt vor, wenn zwei Threads gleichzeitig versuchen, eine Ressource zu sperren, die der jeweils andere Thread bereits gesperrt hat.A deadlock occurs when each of two threads tries to lock a resource the other has already locked. Keiner der beiden Threads kann weiter fortgesetzt werden.Neither thread can make any further progress.

Viele Methoden der Klassen des verwalteten Threadings stellen Timeouts bereit, mit deren Hilfe Sie Deadlocks entdecken können.Many methods of the managed threading classes provide time-outs to help you detect deadlocks. Im folgenden Code wird beispielsweise versucht, ein Objekt namens lockObject zu sperren.For example, the following code attempts to acquire a lock on an object named lockObject. Wenn die Sperrung nicht innerhalb von 300 Millisekunden erfolgt, gibt Monitor.TryEnter den Wert false zurück.If the lock is not obtained in 300 milliseconds, Monitor.TryEnter returns false.

If Monitor.TryEnter(lockObject, 300) Then  
    Try  
        ' Place code protected by the Monitor here.  
    Finally  
        Monitor.Exit(lockObject)  
    End Try  
Else  
    ' Code to execute if the attempt times out.  
End If  
if (Monitor.TryEnter(lockObject, 300)) {  
    try {  
        // Place code protected by the Monitor here.  
    }  
    finally {  
        Monitor.Exit(lockObject);  
    }  
}  
else {  
    // Code to execute if the attempt times out.  
}  

RacebedingungenRace conditions

Bei einer Racebedingung handelt es sich um einen Fehler, der auftritt, wenn das Ergebnis eines Programms davon abhängt, welcher von zwei oder mehr Threads einen bestimmten Codeblock zuerst erreicht.A race condition is a bug that occurs when the outcome of a program depends on which of two or more threads reaches a particular block of code first. Wenn Sie das Programm mehrmals ausführen, werden verschiedene Ergebnisse erzeugt, und das Ergebnis einer bestimmten Ausführung lässt sich nicht vorhersagen.Running the program many times produces different results, and the result of any given run cannot be predicted.

Ein einfaches Beispiel einer Racebedingung ist die Erhöhung eines Feldes.A simple example of a race condition is incrementing a field. Angenommen, eine Klasse besitzt ein privates static-Feld (Shared in Visual Basic), das jedes Mal, wenn eine Instanz der Klasse erstellt wird, mit Code wie objCt++; (C#) oder objCt += 1 (Visual Basic) erhöht wird.Suppose a class has a private static field (Shared in Visual Basic) that is incremented every time an instance of the class is created, using code such as objCt++; (C#) or objCt += 1 (Visual Basic). Für diesen Vorgang muss der Wert von objCt in ein Register geladen, der Wert erhöht und in objCt gespeichert werden.This operation requires loading the value from objCt into a register, incrementing the value, and storing it in objCt.

In einer Multithreadanwendung kann ein Thread, der den Wert geladen und erhöht hat, von einem anderen Thread präemptiv unterbrochen werden, der dann alle drei Schritte ausführt. Wenn der erste Thread die Ausführung fortsetzt und den Wert speichert, überschreibt er objCt, ohne dass dabei berücksichtigt wird, dass der Wert in der Zwischenzeit geändert wurde.In a multithreaded application, a thread that has loaded and incremented the value might be preempted by another thread which performs all three steps; when the first thread resumes execution and stores its value, it overwrites objCt without taking into account the fact that the value has changed in the interim.

Diese spezielle Racebedingung lässt sich mit den Methoden der Interlocked-Klasse (z. B. Interlocked.Increment) problemlos vermeiden.This particular race condition is easily avoided by using methods of the Interlocked class, such as Interlocked.Increment. Weitere Techniken zum Synchronisieren von Daten zwischen mehreren Threads finden Sie unter Synchronizing Data for Multithreading (Synchronisieren von Daten für Multithreading).To read about other techniques for synchronizing data among multiple threads, see Synchronizing Data for Multithreading.

Racebedingungen können auch auftreten, wenn Sie die Aktivitäten von mehreren Threads synchronisieren.Race conditions can also occur when you synchronize the activities of multiple threads. Bei jeder Codezeile, die Sie schreiben, müssen Sie sich überlegen, was passieren kann, wenn ein Thread vor der Ausführung der Zeile (oder jeder einzelnen Anweisung, aus denen die Zeile besteht) präemptiv unterbrochen und die Ausführung von einem anderen Thread fortgesetzt wird.Whenever you write a line of code, you must consider what might happen if a thread were preempted before executing the line (or before any of the individual machine instructions that make up the line), and another thread overtook it.

Statische Member und statische KonstruktorenStatic members and static constructors

Eine Klasse wird erst dann initialisiert, wenn ihr Klassenkonstruktor (static-Konstruktor in C#, Shared Sub New in Visual Basic) nicht mehr ausgeführt wird.A class is not initialized until its class constructor (static constructor in C#, Shared Sub New in Visual Basic) has finished running. Um die Codeausführung für einen nicht initialisierten Typ zu verhindern, blockiert die Common Language Runtime alle Aufrufe von anderen Threads an static-Member der Klasse (Shared-Member in Visual Basic), bis der Klassenkonstruktor nicht mehr ausgeführt wird.To prevent the execution of code on a type that is not initialized, the common language runtime blocks all calls from other threads to static members of the class (Shared members in Visual Basic) until the class constructor has finished running.

Wenn ein Klassenkonstruktor zum Beispiel einen neuen Thread startet und die Threadprozedur einen static-Member der Klasse aufruft, wird der neue Thread blockiert, bis der Klassenkonstruktor beendet wird.For example, if a class constructor starts a new thread, and the thread procedure calls a static member of the class, the new thread blocks until the class constructor completes.

Dies gilt für jeden Typ, der über einen static-Konstruktor verfügen kann.This applies to any type that can have a static constructor.

Anzahl von ProzessorenNumber of processors

Die Multithreadarchitektur kann davon beeinflusst werden, ob das System mehrere Prozessoren oder nur einen Prozessor enthält.Whether there are multiple processors or only one processor available on a system can influence multithreaded architecture. Weitere Informationen finden Sie unter Anzahl von Prozessoren.For more information, see Number of Processors.

Verwenden Sie die Eigenschaft Environment.ProcessorCount, um die zur Laufzeit verfügbare Anzahl von Prozessoren zu bestimmen.Use the Environment.ProcessorCount property to determine the number of processors available at runtime.

Allgemeine EmpfehlungenGeneral recommendations

Beachten Sie bei Verwendung von mehreren Threads die folgenden Richtlinien:Consider the following guidelines when using multiple threads:

  • Verwenden Sie Thread.Abort nicht, um andere Threads zu beenden.Don't use Thread.Abort to terminate other threads. Wenn Sie Abort für einen anderen Thread aufrufen, wird für diesen Thread mit hoher Wahrscheinlichkeit eine Ausnahme ausgelöst, ohne dass Sie wissen, an welchem Punkt die Verarbeitung unterbrochen wurde.Calling Abort on another thread is akin to throwing an exception on that thread, without knowing what point that thread has reached in its processing.

  • Verwenden Sie Thread.Suspend und Thread.Resume, nicht, um die Aktivitäten mehrerer Threads zu synchronisieren.Don't use Thread.Suspend and Thread.Resume to synchronize the activities of multiple threads. Verwenden Sie Mutex, ManualResetEvent, AutoResetEvent und Monitor.Do use Mutex, ManualResetEvent, AutoResetEvent, and Monitor.

  • Steuern Sie die Ausführung von Arbeitsthreads nicht vom Hauptprogramm aus (beispielsweise unter Verwendung von Ereignissen).Don't control the execution of worker threads from your main program (using events, for example). Konzipieren Sie das Programm hingegen so, dass Arbeitsthreads dafür zuständig sind, auf auszuführende Aufgaben zu warten, diese auszuführen und die anderen Teile des Programms darüber zu informieren, dass die Aufgaben erledigt wurden.Instead, design your program so that worker threads are responsible for waiting until work is available, executing it, and notifying other parts of your program when finished. Bei nicht blockierenden Arbeitsthreads sollten Sie u. U. Threadpoolthreads verwenden.If your worker threads do not block, consider using thread pool threads. Monitor.PulseAll ist in Situationen nützlich, in denen Arbeitsthreads blockieren.Monitor.PulseAll is useful in situations where worker threads block.

  • Verwenden Sie keine Typen als Sperrobjekte.Don't use types as lock objects. Das bedeutet, dass Sie Code wie lock(typeof(X)) in C# oder SyncLock(GetType(X)) in Visual Basic genauso wie die Verwendung von Monitor.Enter mit Type-Objekten vermeiden sollten.That is, avoid code such as lock(typeof(X)) in C# or SyncLock(GetType(X)) in Visual Basic, or the use of Monitor.Enter with Type objects. Für einen entsprechenden Typ gibt es nur eine Instanz von System.Type pro Anwendungsdomäne.For a given type, there is only one instance of System.Type per application domain. Wenn der von Ihnen gesperrte Typ öffentlich ist, kann er von fremdem Code gesperrt werden, was zu Deadlocks führt.If the type you take a lock on is public, code other than your own can take locks on it, leading to deadlocks. Weitere Informationen finden Sie unter Reliability Best Practices (Empfohlene Vorgehensweise für Zuverlässigkeit).For additional issues, see Reliability Best Practices.

  • Seien Sie beim Sperren von Instanzen vorsichtig, zum Beispiel lock(this) in C# oder SyncLock(Me) in Visual Basic.Use caution when locking on instances, for example lock(this) in C# or SyncLock(Me) in Visual Basic. Wenn anderer, für den Typ externer Code in der Anwendung das Objekt sperrt, könnte dies zu Deadlocks führen.If other code in your application, external to the type, takes a lock on the object, deadlocks could occur.

  • Stellen Sie sicher, dass ein Thread, der in einem Monitor überwacht wird, den Monitor verlässt, auch wenn eine Ausnahme auftritt, während der Thread sich im Monitor befindet.Do ensure that a thread that has entered a monitor always leaves that monitor, even if an exception occurs while the thread is in the monitor. Die C#-Anweisung lock und die Visual Basic-Anweisung SyncLock stellen dieses Verhalten automatisch bereit, wobei sie einen finally-Block verwenden, um sicherzustellen, dass Monitor.Exit aufgerufen wird.The C# lock statement and the Visual Basic SyncLock statement provide this behavior automatically, employing a finally block to ensure that Monitor.Exit is called. Wenn Sie den Aufruf von Exit nicht sicherstellen können, empfiehlt sich stattdessen die Verwendung von Mutex.If you cannot ensure that Exit will be called, consider changing your design to use Mutex. Ein Mutex wird automatisch freigegeben, wenn der Thread, in dessen Besitz er sich befindet, beendet wird.A mutex is automatically released when the thread that currently owns it terminates.

  • Verwenden Sie mehrere Threads nicht für Aufgaben, für die verschiedene Ressourcen benötigt werden, und vermeiden Sie es, mehrere Threads einer einzelnen Ressource zuzuweisen.Do use multiple threads for tasks that require different resources, and avoid assigning multiple threads to a single resource. Aufgaben, die Ein- und Ausgaben umfassen, sollten beispielsweise über einen eigenen Thread verfügen, da der entsprechende Thread während der E/A-Vorgänge blockiert wird. Dies ermöglicht die Ausführung von anderen Threads.For example, any task involving I/O benefits from having its own thread, because that thread will block during I/O operations and thus allow other threads to execute. Benutzereingaben sind ein weiteres Beispiel für eine Ressource, die von einem für sie reservierten Thread profitiert.User input is another resource that benefits from a dedicated thread. Auf einem Computer mit einem einzelnen Prozessor können eine Aufgabe, die eine ressourcenintensive Berechnung erfordert, und Benutzereingaben oder Aufgaben mit E/A-Vorgängen problemlos gleichzeitig ausgeführt werden. Mehrere rechenintensive Aufgaben machen sich jedoch gegenseitig die Ressourcen streitig.On a single-processor computer, a task that involves intensive computation coexists with user input and with tasks that involve I/O, but multiple computation-intensive tasks contend with each other.

  • Möglicherweise empfiehlt es sich, für einfache Zustandsänderungen statt der Interlocked-Anweisung (lock in Visual Basic) Methoden der SyncLock-Klasse zu verwenden.Consider using methods of the Interlocked class for simple state changes, instead of using the lock statement (SyncLock in Visual Basic). Die lock-Anweisung ist für allgemeine Zwecke gut geeignet, doch für Aktualisierungen, die atomar sein müssen, bietet die Interlocked-Klasse eine bessere Leistung.The lock statement is a good general-purpose tool, but the Interlocked class provides better performance for updates that must be atomic. Sie führt intern ein einzelnes lock-Präfix aus, wenn kein Konflikt vorliegt.Internally, it executes a single lock prefix if there is no contention. Achten Sie bei Codeüberprüfungen auf Code wie in den folgenden Beispielen.In code reviews, watch for code like that shown in the following examples. Im ersten Beispiel wird eine Zustandsvariable erhöht:In the first example, a state variable is incremented:

    SyncLock lockObject  
        myField += 1  
    End SyncLock  
    
    lock(lockObject)   
    {  
        myField++;  
    }  
    

    Sie können die Leistung verbessern, indem Sie statt der Increment-Anweisung die lock-Methode wie folgt verwenden:You can improve performance by using the Increment method instead of the lock statement, as follows:

    System.Threading.Interlocked.Increment(myField)  
    
    System.Threading.Interlocked.Increment(myField);  
    

    Hinweis

    Verwenden Sie im .NET Framework 2.0 und höher die Add-Methode für unteilbare Inkremente größer als 1.In the .NET Framework 2.0 and later, use the Add method for atomic increments larger than 1.

    Im zweiten Beispiel wird eine Referenztypvariable nur aktualisiert, wenn es sich um einen NULL-Verweis (Nothing in Visual Basic) handelt.In the second example, a reference type variable is updated only if it is a null reference (Nothing in Visual Basic).

    If x Is Nothing Then  
        SyncLock lockObject  
            If x Is Nothing Then  
                x = y  
            End If  
        End SyncLock  
    End If  
    
    if (x == null)  
    {  
        lock (lockObject)  
        {  
            if (x == null)  
            {  
                x = y;  
            }  
        }  
    }  
    

    Die Leistung kann verbessert werden, indem stattdessen die CompareExchange-Methode wie folgt verwendet wird:Performance can be improved by using the CompareExchange method instead, as follows:

    System.Threading.Interlocked.CompareExchange(x, y, Nothing)  
    
    System.Threading.Interlocked.CompareExchange(ref x, y, null);  
    

    Hinweis

    Ab .NET Framework 2.0 bietet die Überladung der CompareExchange<T>(T, T, T)-Methoden eine typsichere Alternative für Verweistypen.Beginning with .NET Framework 2.0, the CompareExchange<T>(T, T, T) method overload provides a type-safe alternative for reference types.

Empfehlungen für KlassenbibliothekenRecommendations for class libraries

Beachten Sie die folgenden Richtlinien, wenn Sie Klassenbibliotheken für Multithreading entwerfen:Consider the following guidelines when designing class libraries for multithreading:

  • Vermeiden Sie nach Möglichkeit die Notwendigkeit einer Synchronisierung.Avoid the need for synchronization, if possible. Dies gilt besonders für häufig verwendeten Code.This is especially true for heavily used code. Beispielsweise kann ein Algorithmus angepasst werden, um eine Racebedingung zu tolerieren und nicht zu unterbinden.For example, an algorithm might be adjusted to tolerate a race condition rather than eliminate it. Durch unnötige Synchronisierung wird die Leistung verringert, und es entsteht die Gefahr von Deadlocks und Racebedingungen.Unnecessary synchronization decreases performance and creates the possibility of deadlocks and race conditions.

  • Machen Sie statische Daten (Shared in Visual Basic) standardmäßig threadsicher.Make static data (Shared in Visual Basic) thread safe by default.

  • Legen Sie Instanzdaten nicht als standardmäßig threadsicher fest.Do not make instance data thread safe by default. Durch Hinzufügen von Sperren, um threadsicheren Code zu erstellen, wird die Leistung beeinträchtigt, die Sperrenkonflikte werden erhöht, und es entsteht die Gefahr von Deadlocks.Adding locks to create thread-safe code decreases performance, increases lock contention, and creates the possibility for deadlocks to occur. Bei den gängigen Anwendungsmodellen führt immer nur ein Thread Benutzercode aus, wodurch die Notwendigkeit der Threadsicherheit reduziert wird.In common application models, only one thread at a time executes user code, which minimizes the need for thread safety. Aus diesem Grund sind .NET Framework-Klassenbibliotheken nicht standardmäßig threadsicher.For this reason, the .NET Framework class libraries are not thread safe by default.

  • Vermeiden Sie die Bereitstellung von statischen Methoden, die den statischen Zustand ändern.Avoid providing static methods that alter static state. Bei den üblichen Serverszenarios wird der statische Zustand in mehreren Anforderungen gemeinsam genutzt, d. h., mehrere Threads können den Code gleichzeitig ausführen.In common server scenarios, static state is shared across requests, which means multiple threads can execute that code at the same time. Dadurch werden Threadingfehler möglich.This opens up the possibility of threading bugs. Verwenden Sie u. U. ein Entwurfsmuster, bei dem Daten in Instanzen gekapselt werden, die nicht in mehreren Anforderungen gemeinsam genutzt werden.Consider using a design pattern that encapsulates data into instances that are not shared across requests. Darüber hinaus können bei der Synchronisierung statischer Daten Aufrufe zwischen statischen Methoden, die den Zustand ändern, zu Deadlocks oder redundanter Synchronisierung führen und die Leistung beeinträchtigen.Furthermore, if static data are synchronized, calls between static methods that alter state can result in deadlocks or redundant synchronization, adversely affecting performance.

Siehe auchSee also