TaskScheduler Class
Represents an object that handles the low-level work of queuing tasks onto threads.
Assembly: mscorlib (in mscorlib.dll)
| Name | Description | |
|---|---|---|
![]() | TaskScheduler() | Initializes the TaskScheduler. |
| Name | Description | |
|---|---|---|
![]() ![]() | Current | Gets the TaskScheduler associated with the currently executing task. |
![]() ![]() | Default | Gets the default TaskScheduler instance that is provided by the .NET Framework. |
![]() | Id | Gets the unique ID for this TaskScheduler. |
![]() | MaximumConcurrencyLevel | Indicates the maximum concurrency level this TaskScheduler is able to support. |
| Name | Description | |
|---|---|---|
![]() | Equals(Object) | Determines whether the specified object is equal to the current object.(Inherited from Object.) |
![]() | Finalize() | Allows an object to try to free resources and perform other cleanup operations before it is reclaimed by garbage collection.(Inherited from Object.) |
![]() ![]() | FromCurrentSynchronizationContext() | Creates a TaskScheduler associated with the current System.Threading.SynchronizationContext. |
![]() | GetHashCode() | Serves as the default hash function. (Inherited from Object.) |
![]() | GetScheduledTasks() | For debugger support only, generates an enumerable of Task instances currently queued to the scheduler waiting to be executed. |
![]() | GetType() | |
![]() | MemberwiseClone() | |
![]() | QueueTask(Task) | Queues a Task to the scheduler. |
![]() | ToString() | Returns a string that represents the current object.(Inherited from Object.) |
![]() | TryDequeue(Task) | Attempts to dequeue a Task that was previously queued to this scheduler. |
![]() | TryExecuteTask(Task) | Attempts to execute the provided Task on this scheduler. |
![]() | TryExecuteTaskInline(Task, Boolean) | Determines whether the provided Task can be executed synchronously in this call, and if it can, executes it. |
| Name | Description | |
|---|---|---|
![]() ![]() | UnobservedTaskException | Occurs when a faulted task's unobserved exception is about to trigger exception escalation policy, which, by default, would terminate the process. |
An instance of the TaskScheduler class represents a task scheduler. A task scheduler ensures that the work of a task is eventually executed.
The default task scheduler is based on the .NET Framework 4 thread pool, which provides work-stealing for load-balancing, thread injection/retirement for maximum throughput, and overall good performance. It should be sufficient for most scenarios.
The TaskScheduler class also serves as the extension point for all customizable scheduling logic. This includes mechanisms such as how to schedule a task for execution, and how scheduled tasks should be exposed to debuggers. If you require special functionality, you can create a custom scheduler and enable it for specific tasks or queries.
In this topic:
The default task scheduler and the thread pool
The global queue vs. local queues
Work stealing
Long-running tasks
Task inlining
Specifying a synchronization context
The default scheduler for the Task Parallel Library and PLINQ uses the .NET Framework thread pool, which is represented by the ThreadPool class, to queue and execute work. The thread pool uses the information that is provided by the Task type to efficiently support the fine-grained parallelism (short-lived units of work) that parallel tasks and queries often represent.
The thread pool maintains a global FIFO (first-in, first-out) work queue for threads in each application domain. Whenever a program calls the ThreadPool.QueueUserWorkItem (or ThreadPool.UnsafeQueueUserWorkItem) method, the work is put on this shared queue and eventually de-queued onto the next thread that becomes available. Starting with the .NET Framework 4, this queue has been improved to use a lock-free algorithm that resembles the ConcurrentQueue(Of T) class. By using this lock-free implementation, the thread pool spends less time when it queues and de-queues work items. This performance benefit is available to all programs that use the thread pool.
Top-level tasks, which are tasks that are not created in the context of another task, are put on the global queue just like any other work item. However, nested or child tasks, which are created in the context of another task, are handled quite differently. A child or nested task is put on a local queue that is specific to the thread on which the parent task is executing. The parent task may be a top-level task or it also may be the child of another task. When this thread is ready for more work, it first looks in the local queue. If work items are waiting there, they can be accessed quickly. The local queues are accessed in last-in, first-out order (LIFO) to preserve cache locality and reduce contention. For more information about child tasks and nested tasks, see Attached and Detached Child Tasks.
The use of local queues not only reduces pressure on the global queue, but also takes advantage of data locality. Work items in the local queue frequently reference data structures that are physically near one another in memory. In these cases, the data is already in the cache after the first task has run and can be accessed quickly. Both Parallel LINQ (PLINQ) and the Parallel class use nested tasks and child tasks extensively, and achieve significant speedups by using the local work queues.
Starting with the .NET Framework 4, the thread pool also features a work-stealing algorithm to help make sure that no threads are sitting idle while others still have work in their queues. When a thread-pool thread is ready for more work, it first looks at the head of its local queue, then in the global queue, and then in the local queues of other threads. If it finds a work item in the local queue of another thread, it first applies heuristics to make sure that it can run the work efficiently. If it can, it de-queues the work item from the tail (in FIFO order). This reduces contention on each local queue and preserves data locality. This architecture helps the thread pool load-balance work more efficiently than past versions did.
You may want to explicitly prevent a task from being put on a local queue. For example, you may know that a particular work item will run for a relatively long time and is likely to block all other work items on the local queue. In this case, you can specify the TaskCreationOptions.LongRunning option, which provides a hint to the scheduler that an additional thread might be required for the task so that it does not block the forward progress of other threads or work items on the local queue. By using this option you avoid the thread pool completely, including the global and local queues.
In some cases when a Task is waited on, it may be executed synchronously on the thread that is performing the wait operation. This enhances performance by preventing the need for an additional thread and instead using the existing thread, which would have blocked otherwise. To prevent errors due to re-entrancy, task inlining only occurs when the wait target is found in the relevant thread's local queue.
You can use the TaskScheduler.FromCurrentSynchronizationContext method to specify that a task should be scheduled to run on a particular thread. This is useful in frameworks such as Windows Forms and Windows Presentation Foundation where access to user interface objects is often restricted to code that is running on the same thread on which the UI object was created. For more information, see How to: Schedule Work on the User Interface (UI) Thread.
The following example uses the TaskScheduler.FromCurrentSynchronizationContext method in a Windows Presentation Foundation (WPF) app to schedule a task on the same thread that the user interface (UI) control was created on. The example creates a mosaic of images that are randomly selected from a specified directory. The WPF objects are used to load and resize the images. The raw pixels are then passed to a task that uses a For loop to write the pixel data into a large single-byte array. No synchronization is required because no two tiles occupy the same array elements. The tiles can also be written in any order because their position is calculated independently of any other tile. The large array is then passed to a task that runs on the UI thread, where the pixel data is loaded into an Image control.
The example moves data off the UI thread, modifies it by using parallel loops and Task objects, and then passes it back to a task that runs on the UI thread. This approach is useful when you have to use the Task Parallel Library to perform operations that either are not supported by the WPF API, or are not sufficiently fast. Another way to create an image mosaic in WPF is to use a System.Windows.Controls.WrapPanel control and add images to it. The WrapPanel handles the work of positioning the tiles. However, this work can only be performed on the UI thread.
Imports System.Threading.Tasks Imports System.Windows Imports System.Windows.Media Imports System.Windows.Media.Imaging Partial Public Class MainWindow : Inherits Window Dim fileCount As Integer Dim colCount As Integer Dim rowCount As Integer Dim tilePixelHeight As Integer Dim tilePixelWidth As Integer Dim largeImagePixelHeight As Integer Dim largeImagePixelWidth As Integer Dim largeImageStride As Integer Dim format As PixelFormat Dim palette As BitmapPalette = Nothing Public Sub New() InitializeComponent() ' For this example, values are hard-coded to a mosaic of 8x8 tiles. ' Each tile Is 50 pixels high And 66 pixels wide And 32 bits per pixel. colCount = 12 rowCount = 8 tilePixelHeight = 50 tilePixelWidth = 66 largeImagePixelHeight = tilePixelHeight * rowCount largeImagePixelWidth = tilePixelWidth * colCount largeImageStride = largeImagePixelWidth * (32 / 8) Me.Width = largeImagePixelWidth + 40 image.Width = largeImagePixelWidth image.Height = largeImagePixelHeight End Sub Private Sub button_Click(sender As Object, e As RoutedEventArgs) _ Handles button.Click ' For best results use 1024 x 768 jpg files at 32bpp. Dim files() As String = System.IO.Directory.GetFiles("C:\Users\Public\Pictures\Sample Pictures\", "*.jpg") fileCount = files.Length Dim images(fileCount - 1) As Task(Of Byte()) For i As Integer = 0 To fileCount - 1 Dim x As Integer = i images(x) = Task.Factory.StartNew(Function() LoadImage(files(x))) Next ' When they�ve all been loaded, tile them into a single byte array. 'var tiledImage = Task.Factory.ContinueWhenAll( ' images, (i) >= TileImages(i)); ' Dim tiledImage As Task(Of Byte()) = Task.Factory.ContinueWhenAll(images, Function(i As Task(Of Byte())) TileImages(i)) Dim tiledImage = Task.Factory.ContinueWhenAll(images, Function(i As Task(Of Byte())()) TileImages(i)) ' We are currently on the UI thread. Save the sync context And pass it to ' the next task so that it can access the UI control "image1". Dim UISyncContext = TaskScheduler.FromCurrentSynchronizationContext() ' On the UI thread, put the bytes into a bitmap And ' And display it in the Image control. Dim t3 = tiledImage.ContinueWith(Sub(antecedent) ' Get System DPI. Dim m As Matrix = PresentationSource.FromVisual(Application.Current.MainWindow).CompositionTarget.TransformToDevice Dim dpiX As Double = m.M11 Dim dpiY As Double = m.M22 ' Use the default palette in creating the bitmap. Dim bms As BitmapSource = BitmapSource.Create(largeImagePixelWidth, largeImagePixelHeight, dpiX, dpiY, format, palette, antecedent.Result, largeImageStride) image.Source = bms End Sub, UISyncContext) End Sub Public Function LoadImage(filename As String) As Byte() ' Use the WPF BitmapImage class to load And ' resize the bitmap. NOTE: Only 32bpp formats are supported correctly. ' Support for additional color formats Is left as an exercise ' for the reader. For more information, see documentation for ColorConvertedBitmap. Dim bitmapImage As New BitmapImage() bitmapImage.BeginInit() bitmapImage.UriSource = New Uri(filename) bitmapImage.DecodePixelHeight = tilePixelHeight bitmapImage.DecodePixelWidth = tilePixelWidth bitmapImage.EndInit() format = bitmapImage.Format Dim size As Integer = CInt(bitmapImage.Height * bitmapImage.Width) Dim stride As Integer = CInt(bitmapImage.Width * 4) Dim dest(stride * tilePixelHeight - 1) As Byte bitmapImage.CopyPixels(dest, stride, 0) Return dest End Function Function Stride(pixelWidth As Integer, bitsPerPixel As Integer) As Integer Return (((pixelWidth * bitsPerPixel + 31) / 32) * 4) End Function ' Map the individual image tiles to the large image ' in parallel. Any kind of raw image manipulation can be ' done here because we are Not attempting to access any ' WPF controls from multiple threads. Function TileImages(sourceImages As Task(Of Byte())()) As Byte() Dim largeImage(largeImagePixelHeight * largeImageStride - 1) As Byte Dim tileImageStride As Integer = tilePixelWidth * 4 ' hard coded To 32bpp Dim rand As New Random() Parallel.For(0, rowCount * colCount, Sub(i) ' Pick one of the images at random for this tile. Dim cur As Integer = rand.Next(0, sourceImages.Length) Dim pixels() As Byte = sourceImages(cur).Result ' Get the starting index for this tile. Dim row As Integer = i \ colCount Dim col As Integer = i Mod colCount Dim idx As Integer = ((row * (largeImageStride * tilePixelHeight)) + (col * tileImageStride)) ' Write the pixels for the current tile. The pixels are Not contiguous ' in the array, therefore we have to advance the index by the image stride ' (minus the stride of the tile) for each scanline of the tile. Dim tileImageIndex As Integer = 0 For j As Integer = 0 To tilePixelHeight - 1 ' Write the next scanline for this tile. For k As Integer = 0 To tileImageStride - 1 largeImage(idx) = pixels(tileImageIndex) idx += 1 tileImageIndex += 1 Next ' Advance to the beginning of the next scanline. idx += largeImageStride - tileImageStride Next End Sub) Return largeImage End Function End Class
To create the example, crate a WPF application project in Visual Studio and assign it a name of your choice. Then do the following:
In design view, drag an Image control from the Toolbox to the design surface. In XAML view, specify the horizontal alignment as "Left." The size does not matter because the control is be dynamically resized at run time. Accept the default name, "image".
Drag a Button control from the Toolbox to the lower left part of the application window. Double-click the button to add a Click event handler. In XAML view, specify the Content property of the button as "Make a Mosaic" and specify its horizontal alignment as "Left". Accept the default name, "button".
Replace the entire contents of the MainWindow.xaml.cs or MainWindow.xaml.vb file with the code from this example. Make sure that the name of the workspace matches the project name.
The example reads JPEG images from a directory named C:\Users\Public\Pictures\Sample Pictures\. Either create the directory and place some images in it, or change the path to refer to some other directory that contains images.
This example has some limitations. For example, only 32-bits-per-pixel images are supported; images in other formats are corrupted by the BitmapImage object during the resizing operation. Also, the source images must all be larger than the tile size. As a further exercise, you can add functionality to handle multiple pixel formats and file sizes.
The following example is taken from the Samples for Parallel Programming with the .NET Framework 4 on the MSDN Code Gallery Web site. It creates a custom task scheduler that limits the number of threads used by the app. It then launches two sets of tasks and displays information about the task and the thread on which the task is executing.
Imports System.Collections.Generic Imports System.Threading Imports System.Threading.Tasks Module Example Sub Main() ' Create a scheduler that uses two threads. Dim lcts As New LimitedConcurrencyLevelTaskScheduler(2) Dim tasks As New List(Of Task)() ' Create a TaskFactory and pass it our custom scheduler. Dim factory As New TaskFactory(lcts) Dim cts As New CancellationTokenSource() ' Use our factory to run a set of tasks. Dim objLock As New Object() Dim outputItem As Integer For tCtr As Integer = 0 To 4 Dim iteration As Integer = tCtr Dim t As Task = factory.StartNew(Sub() For i As Integer = 1 To 1000 SyncLock objLock Console.Write("{0} in task t-{1} on thread {2} ", i, iteration, Thread.CurrentThread.ManagedThreadId) outputItem += 1 If outputItem Mod 3 = 0 Then Console.WriteLine() End SyncLock Next End Sub, cts.Token) tasks.Add(t) Next ' Use it to run a second set of tasks. For tCtr As Integer = 0 To 4 Dim iteration As Integer = tCtr Dim t1 As Task = factory.StartNew(Sub() For outer As Integer = 0 To 10 For i As Integer = &h21 To &h7E SyncLock objLock Console.Write("'{0}' in task t1-{1} on thread {2} ", Convert.ToChar(i), iteration, Thread.CurrentThread.ManagedThreadId) outputItem += 1 If outputItem Mod 3 = 0 Then Console.WriteLine() End SyncLock Next Next End Sub, cts.Token) tasks.Add(t1) Next ' Wait for the tasks to complete before displaying a completion message. Task.WaitAll(tasks.ToArray()) cts.Dispose() Console.WriteLine(vbCrLf + vbCrLf + "Successful completion.") End Sub End Module ' Provides a task scheduler that ensures a maximum concurrency level while ' running on top of the thread pool. Public Class LimitedConcurrencyLevelTaskScheduler : Inherits TaskScheduler ' Indicates whether the current thread is processing work items. <ThreadStatic()> Private Shared _currentThreadIsProcessingItems As Boolean ' The list of tasks to be executed Private ReadOnly _tasks As LinkedList(Of Task) = New LinkedList(Of Task)() 'The maximum concurrency level allowed by this scheduler. Private ReadOnly _maxDegreeOfParallelism As Integer ' Indicates whether the scheduler is currently processing work items. Private _delegatesQueuedOrRunning As Integer = 0 ' protected by lock(_tasks) ' Creates a new instance with the specified degree of parallelism. Public Sub New(ByVal maxDegreeOfParallelism As Integer) If (maxDegreeOfParallelism < 1) Then Throw New ArgumentOutOfRangeException("maxDegreeOfParallelism") End If _maxDegreeOfParallelism = maxDegreeOfParallelism End Sub ' Queues a task to the scheduler. Protected Overrides Sub QueueTask(ByVal t As Task) ' Add the task to the list of tasks to be processed. If there aren't enough ' delegates currently queued or running to process tasks, schedule another. SyncLock (_tasks) _tasks.AddLast(t) If (_delegatesQueuedOrRunning < _maxDegreeOfParallelism) Then _delegatesQueuedOrRunning = _delegatesQueuedOrRunning + 1 NotifyThreadPoolOfPendingWork() End If End SyncLock End Sub ' Inform the ThreadPool that there's work to be executed for this scheduler. Private Sub NotifyThreadPoolOfPendingWork() ThreadPool.UnsafeQueueUserWorkItem(Sub() ' Note that the current thread is now processing work items. ' This is necessary to enable inlining of tasks into this thread. _currentThreadIsProcessingItems = True Try ' Process all available items in the queue. While (True) Dim item As Task SyncLock (_tasks) ' When there are no more items to be processed, ' note that we're done processing, and get out. If (_tasks.Count = 0) Then _delegatesQueuedOrRunning = _delegatesQueuedOrRunning - 1 Exit While End If ' Get the next item from the queue item = _tasks.First.Value _tasks.RemoveFirst() End SyncLock ' Execute the task we pulled out of the queue MyBase.TryExecuteTask(item) End While ' We're done processing items on the current thread Finally _currentThreadIsProcessingItems = False End Try End Sub, Nothing) End Sub ' Attempts to execute the specified task on the current thread. Protected Overrides Function TryExecuteTaskInline(ByVal t As Task, ByVal taskWasPreviouslyQueued As Boolean) As Boolean ' If this thread isn't already processing a task, we don't support inlining If (Not _currentThreadIsProcessingItems) Then Return False End If ' If the task was previously queued, remove it from the queue If (taskWasPreviouslyQueued) Then ' Try to run the task. If TryDequeue(t) Then Return MyBase.TryExecuteTask(t) Else Return False End If Else Return MyBase.TryExecuteTask(t) End If End Function ' Attempt to remove a previously scheduled task from the scheduler. Protected Overrides Function TryDequeue(ByVal t As Task) As Boolean SyncLock (_tasks) Return _tasks.Remove(t) End SyncLock End Function ' Gets the maximum concurrency level supported by this scheduler. Public Overrides ReadOnly Property MaximumConcurrencyLevel As Integer Get Return _maxDegreeOfParallelism End Get End Property ' Gets an enumerable of the tasks currently scheduled on this scheduler. Protected Overrides Function GetScheduledTasks() As IEnumerable(Of Task) Dim lockTaken As Boolean = False Try Monitor.TryEnter(_tasks, lockTaken) If (lockTaken) Then Return _tasks.ToArray() Else Throw New NotSupportedException() End If Finally If (lockTaken) Then Monitor.Exit(_tasks) End If End Try End Function End Class ' The following is a portion of the output from a single run of the example: ' 'T' in task t1-4 on thread 3 'U' in task t1-4 on thread 3 'V' in task t1-4 on thread 3 ' 'W' in task t1-4 on thread 3 'X' in task t1-4 on thread 3 'Y' in task t1-4 on thread 3 ' 'Z' in task t1-4 on thread 3 '[' in task t1-4 on thread 3 '\' in task t1-4 on thread 3 ' ']' in task t1-4 on thread 3 '^' in task t1-4 on thread 3 '_' in task t1-4 on thread 3 ' '`' in task t1-4 on thread 3 'a' in task t1-4 on thread 3 'b' in task t1-4 on thread 3 ' 'c' in task t1-4 on thread 3 'd' in task t1-4 on thread 3 'e' in task t1-4 on thread 3 ' 'f' in task t1-4 on thread 3 'g' in task t1-4 on thread 3 'h' in task t1-4 on thread 3 ' 'i' in task t1-4 on thread 3 'j' in task t1-4 on thread 3 'k' in task t1-4 on thread 3 ' 'l' in task t1-4 on thread 3 'm' in task t1-4 on thread 3 'n' in task t1-4 on thread 3 ' 'o' in task t1-4 on thread 3 'p' in task t1-4 on thread 3 ']' in task t1-2 on thread 4 ' '^' in task t1-2 on thread 4 '_' in task t1-2 on thread 4 '`' in task t1-2 on thread 4 ' 'a' in task t1-2 on thread 4 'b' in task t1-2 on thread 4 'c' in task t1-2 on thread 4 ' 'd' in task t1-2 on thread 4 'e' in task t1-2 on thread 4 'f' in task t1-2 on thread 4 ' 'g' in task t1-2 on thread 4 'h' in task t1-2 on thread 4 'i' in task t1-2 on thread 4 ' 'j' in task t1-2 on thread 4 'k' in task t1-2 on thread 4 'l' in task t1-2 on thread 4 ' 'm' in task t1-2 on thread 4 'n' in task t1-2 on thread 4 'o' in task t1-2 on thread 4 ' 'p' in task t1-2 on thread 4 'q' in task t1-2 on thread 4 'r' in task t1-2 on thread 4 ' 's' in task t1-2 on thread 4 't' in task t1-2 on thread 4 'u' in task t1-2 on thread 4 ' 'v' in task t1-2 on thread 4 'w' in task t1-2 on thread 4 'x' in task t1-2 on thread 4 ' 'y' in task t1-2 on thread 4 'z' in task t1-2 on thread 4 '{' in task t1-2 on thread 4 ' '|' in task t1-2 on thread 4 '}' in task t1-2 on thread 4 '~' in task t1-2 on thread 4 ' 'q' in task t1-4 on thread 3 'r' in task t1-4 on thread 3 's' in task t1-4 on thread 3 ' 't' in task t1-4 on thread 3 'u' in task t1-4 on thread 3 'v' in task t1-4 on thread 3 ' 'w' in task t1-4 on thread 3 'x' in task t1-4 on thread 3 'y' in task t1-4 on thread 3 ' 'z' in task t1-4 on thread 3 '{' in task t1-4 on thread 3 '|' in task t1-4 on thread 3
Available since 8
.NET Framework
Available since 4.0
Portable Class Library
Supported in: portable .NET platforms
Silverlight
Available since 5.0
Windows Phone Silverlight
Available since 8.0
Windows Phone
Available since 8.1
All members of the abstract TaskScheduler type are thread-safe and may be used from multiple threads concurrently.
.jpeg?cs-save-lang=1&cs-lang=vb)
.jpeg?cs-save-lang=1&cs-lang=vb)
.jpeg?cs-save-lang=1&cs-lang=vb)
.jpeg?cs-save-lang=1&cs-lang=vb)
.jpeg?cs-save-lang=1&cs-lang=vb)
In addition, several sample task schedulers are available on Code Gallery: Samples for Parallel Programming with the .NET Framework 4.