Taskplaner

Taskplaner werden durch die System.Threading.Tasks.TaskScheduler-Klasse dargestellt. Durch einen Taskplaner wird sichergestellt, dass die Arbeit einer Aufgabe schließlich ausgeführt wird. Der standardmäßige Taskplaner basiert auf dem .NET Framework 4-Threadpool, der Arbeitsübernahme für Lastenausgleich und Threadeinfügung/-deaktivierung für maximalen Durchsatz ermöglicht und für eine gute Gesamtleistung sorgt. Dies ist für die meisten Szenarien in der Regel ausreichend. Wenn Sie jedoch spezielle Funktionen benötigen, können Sie einen benutzerdefinierten Planer erstellen und ihn für bestimmte Aufgaben oder Abfragen aktivieren. Weitere Informationen zum Erstellen und Verwenden eines benutzerdefinierten Taskplaners finden Sie unter Gewusst wie: Erstellen eines Taskplaners zum Einschränken des Parallelitätsgrads. Weitere Beispiele von benutzerdefinierten Planern finden Sie unter Parallel Extensions Samples auf der MSDN Code Gallery-Website.

Standardtaskplaner und der Threadpool

Der Standardplaner für Task Parallel Library und PLINQ verwendet das ThreadPool-Objekt in .NET Framework, um Arbeit in die Warteschlange zu stellen und auszuführen. In .NET Framework 4 verwendet das ThreadPool-Objekt die Informationen, die vom System.Threading.Tasks.Task-Typ bereitgestellt werden, um den differenzierten Parallelismus (kurzlebige Arbeitseinheiten), den parallele Aufgaben und Abfragen oft darstellen, effizient zu unterstützen.

Threadpool: Globale Warteschlange im Vergleich zulokalen Warteschlangen

Wie in früheren Versionen von .NET Framework behält das ThreadPool-Objekt in jeder Anwendungsdomäne eine globale First In, First Out (FIFO)-Arbeitswarteschlange für Threads bei. Wenn ein Programm QueueUserWorkItem (oder UnsafeQueueUserWorkItem) aufruft, wird die Arbeit in dieser freigegebenen Warteschlange abgelegt und schließlich im nächsten Thread, der verfügbar wird, aus der Warteschlange entfernt. In .NET Framework 4 wurde diese Warteschlange verbessert, um einen sperrenfreien Algorithmus zu verwenden, der der ConcurrentQueue-Klasse ähnelt. Mit dieser sperrenfreien Implementierung benötigt der ThreadPool weniger Zeit, wenn Arbeitsaufgaben in die Warteschlange gestellt bzw. daraus entfernt werden. Dieser Leistungsvorteil steht allen Programmen zur Verfügung, die das ThreadPool-Objekt verwenden.

Aufgaben der obersten Ebene, bei denen es sich um Aufgaben handelt, die nicht im Kontext einer anderen Aufgabe erstellt werden, werden wie jede andere Arbeitsaufgabe in der globalen Warteschlange abgelegt. Geschachtelte oder untergeordnete Aufgaben, die im Kontext einer anderen Aufgabe erstellt werden, werden hingegen anders behandelt. Ein untergeordnetes Element oder eine geschachtelte Aufgabe wird in einer lokalen Warteschlange abgelegt, die speziell für den Thread vorgesehen ist, in dem die übergeordnete Aufgabe ausgeführt wird. Die übergeordnete Aufgabe ist möglicherweise eine Aufgabe der obersten Ebene. Es kann sich jedoch auch um das untergeordnete Element einer anderen Aufgabe handeln. Wenn dieser Thread für weitere Arbeitsvorgänge bereit ist, wird zuerst eine Suche in der lokalen Warteschlange ausgeführt. Wenn darin Arbeitsaufgaben warten, kann darauf schnell zugegriffen werden. Auf die lokalen Warteschlangen wird nach dem Last In, First Out (LIFO)-Prinzip zugegriffen, um den Cacheort beizubehalten und Konflikte zu minimieren. Weitere Informationen zu untergeordneten und geschachtelten Aufgaben finden Sie unter Geschachtelte Aufgaben und untergeordnete Aufgaben.

Im folgenden Beispiel werden Aufgaben gezeigt, die entweder in der globalen oder in der lokalen Warteschlange geplant werden.

Sub QueueTasks()

    ' TaskA is a top level task.
    Dim taskA = Task.Factory.StartNew(Sub()

                                          Console.WriteLine("I was enqueued on the thread pool's global queue.")

                                          ' TaskB is a nested task and TaskC is a child task. Both go to local queue.
                                          Dim taskB = New Task(Sub() Console.WriteLine("I was enqueued on the local queue."))
                                          Dim taskC = New Task(Sub() Console.WriteLine("I was enqueued on the local queue, too."),
                                                                  TaskCreationOptions.AttachedToParent)

                                          taskB.Start()
                                          taskC.Start()

                                      End Sub)
End Sub
void QueueTasks()
{
    // TaskA is a top level task.
    Task taskA = Task.Factory.StartNew( () =>
    {                
        Console.WriteLine("I was enqueued on the thread pool's global queue."); 

        // TaskB is a nested task and TaskC is a child task. Both go to local queue.
        Task taskB = new Task( ()=> Console.WriteLine("I was enqueued on the local queue."));
        Task taskC = new Task(() => Console.WriteLine("I was enqueued on the local queue, too."),
                                TaskCreationOptions.AttachedToParent);

        taskB.Start();
        taskC.Start();

    });
}

Die Verwendung von lokalen Warteschlangen verringert nicht nur den Druck auf die globale Warteschlange, sondern nutzt auch den Ort der Daten. Arbeitsaufgaben in der lokalen Warteschlange verweisen häufig auf Datenstrukturen, die im Arbeitsspeicher physisch nahe beieinander liegen. In diesen Fällen befinden sich die Daten bereits im Cache, nachdem die erste Aufgabe ausgeführt wurde, sodass schneller Zugriff möglich ist. Sowohl für Parallel LINQ (PLINQ) als auch für die Parallel-Klasse werden extensiv geschachtelte und untergeordnete Aufgaben verwendet. Mithilfe der lokalen Warteschlangen werden deutliche Geschwindigkeitssteigerungen erzielt.

Arbeitsübernahme

Das .NET Framework 4ThreadPool-Objekt verfügt auch über einen Arbeitsübernahme-Algorithmus, um sicherzustellen, dass sich keine Threads im Ruhezustand befinden, während in anderen noch Arbeitsvorgänge stattfinden. Wenn ein Threadpoolthread zusätzliche Arbeit übernehmen kann, wird zuerst am Anfang der lokalen Warteschlange, anschließend in der globalen Warteschlange und zuletzt in den lokalen Warteschlangen anderer Threads gesucht. Wenn eine Arbeitsaufgabe in der lokalen Warteschlange eines anderen Threads gefunden wird, wird zunächst Heuristik angewendet, um sicherzustellen, dass die Arbeit effizient ausgeführt werden kann. Wenn dies möglich ist, wird die Arbeitsaufgabe am Ende aus der Warteschlange entfernt (in FIFO-Reihenfolge). Dadurch werden die Konflikte in jeder lokalen Warteschlange verringert und der Datenort beibehalten. Diese Architektur ermöglicht .NET Framework 4 ThreadPool einen effizienteren Lastenausgleich von Arbeit als in früheren Versionen.

Aufgaben mit langer Laufzeit

Unter Umständen möchten Sie ausdrücklich verhindern, dass eine Aufgabe in einer lokalen Warteschlange abgelegt wird. Sie wissen möglicherweise z. B., dass eine bestimmte Arbeitsaufgabe für eine relativ lange Zeit ausgeführt wird und daher wahrscheinlich alle anderen Arbeitsaufgaben in der lokalen Warteschlange blockiert. In diesem Fall können Sie die LongRunning-Option angeben, die den Planer darauf hinweist, dass u. U. ein weiterer Thread für die Aufgabe erforderlich ist, damit der Fortschritt anderer Threads oder Arbeitsaufgaben in der lokalen Warteschlange nicht blockiert wird. Mit dieser Option wird ThreadPool vollständig vermeiden, einschließlich der globalen und lokalen Warteschlangen.

Aufgabeninlining

In einigen Fällen wird dies beim Aufschieben einer Aufgabe möglicherweise synchron in dem Thread ausgeführt, der den Wartevorgang ausführt. Dadurch wird die Leistung verbessert, da kein Bedarf an einem weiteren Thread besteht. Dazu wird der vorhandene Thread verwendet, der andernfalls blockiert worden wäre. Um Fehler aufgrund des Wiedereintritts zu verhindern, tritt Aufgabeninlining nur auf, wenn das Wartevorgangsziel in der lokalen Warteschlange des relevanten Threads gefunden wird.

Angeben eines Synchronisierungskontexts

Sie können mit der TaskScheduler.FromCurrentSynchronizationContext-Methode angeben, dass eine Aufgabe für die Ausführung in einem bestimmten Thread geplant werden soll. Dies ist in Frameworks wie Windows Forms und Windows Presentation Foundation hilfreich, wo der Zugriff auf Benutzeroberflächenobjekte oftmals auf Code beschränkt ist, der in dem Thread ausgeführt wird, in dem das Benutzeroberflächenobjekt erstellt wurde. Weitere Informationen finden Sie unter Gewusst wie: Planen von Arbeiten an einem angegebenen Synchronisierungskontext.

Siehe auch

Referenz

TaskScheduler

Konzepte

Task Parallel Library

Gewusst wie: Planen von Arbeiten an einem angegebenen Synchronisierungskontext

Weitere Ressourcen

Gewusst wie: Erstellen eines Taskplaners zum Einschränken des Parallelitätsgrads

Aufgabenfactorys