TaskScheduler 类

定义

表示一个处理将任务排队到线程中的低级工作的对象。Represents an object that handles the low-level work of queuing tasks onto threads.

public ref class TaskScheduler abstract
public abstract class TaskScheduler
type TaskScheduler = class
Public MustInherit Class TaskScheduler
继承
TaskScheduler

示例

下面的示例摘自 MSDN 代码库网站上的.NET Framework 4 进行并行编程的示例。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.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

class Example
{
   static void Main()
   {
       // Create a scheduler that uses two threads. 
       LimitedConcurrencyLevelTaskScheduler lcts = new LimitedConcurrencyLevelTaskScheduler(2);
       List<Task> tasks = new List<Task>();
       
       // Create a TaskFactory and pass it our custom scheduler. 
       TaskFactory factory = new TaskFactory(lcts);
       CancellationTokenSource cts = new CancellationTokenSource();
       
       // Use our factory to run a set of tasks. 
       Object lockObj = new Object();
       int outputItem = 0;
       
       for (int tCtr = 0; tCtr <= 4; tCtr++) {
          int iteration = tCtr;
          Task t = factory.StartNew(() => {
                                       for (int i = 0; i < 1000; i++) {
                                          lock (lockObj) {
                                             Console.Write("{0} in task t-{1} on thread {2}   ", 
                                                           i, iteration, Thread.CurrentThread.ManagedThreadId);
                                             outputItem++;
                                             if (outputItem % 3 == 0)
                                                Console.WriteLine();
                                          }
                                       }                   
                                    }, cts.Token);
          tasks.Add(t);                      
      }
      // Use it to run a second set of tasks.                       
      for (int tCtr = 0; tCtr <= 4; tCtr++) {
         int iteration = tCtr;
         Task t1 = factory.StartNew(() => {
                                       for (int outer = 0; outer <= 10; outer++) {
                                          for (int i = 0x21; i <= 0x7E; i++) {
                                             lock (lockObj) {
                                                Console.Write("'{0}' in task t1-{1} on thread {2}   ", 
                                                              Convert.ToChar(i), iteration, Thread.CurrentThread.ManagedThreadId);
                                                outputItem++;
                                                if (outputItem % 3 == 0)
                                                   Console.WriteLine();
                                             } 
                                          }
                                       }                                           
                                    }, cts.Token);           
         tasks.Add(t1);
      }
      
      // Wait for the tasks to complete before displaying a completion message.
      Task.WaitAll(tasks.ToArray());
      cts.Dispose();
      Console.WriteLine("\n\nSuccessful completion.");
   }
}

// Provides a task scheduler that ensures a maximum concurrency level while 
// running on top of the thread pool.
public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{
   // Indicates whether the current thread is processing work items.
   [ThreadStatic]
   private static bool _currentThreadIsProcessingItems;

  // The list of tasks to be executed 
   private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); // protected by lock(_tasks)

   // The maximum concurrency level allowed by this scheduler. 
   private readonly int _maxDegreeOfParallelism;

   // Indicates whether the scheduler is currently processing work items. 
   private int _delegatesQueuedOrRunning = 0;

   // Creates a new instance with the specified degree of parallelism. 
   public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
   {
       if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism");
       _maxDegreeOfParallelism = maxDegreeOfParallelism;
   }

   // Queues a task to the scheduler. 
   protected sealed override void QueueTask(Task 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. 
       lock (_tasks)
       {
           _tasks.AddLast(task);
           if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
           {
               ++_delegatesQueuedOrRunning;
               NotifyThreadPoolOfPendingWork();
           }
       }
   }

   // Inform the ThreadPool that there's work to be executed for this scheduler. 
   private void NotifyThreadPoolOfPendingWork()
   {
       ThreadPool.UnsafeQueueUserWorkItem(_ =>
       {
           // 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)
               {
                   Task item;
                   lock (_tasks)
                   {
                       // When there are no more items to be processed,
                       // note that we're done processing, and get out.
                       if (_tasks.Count == 0)
                       {
                           --_delegatesQueuedOrRunning;
                           break;
                       }

                       // Get the next item from the queue
                       item = _tasks.First.Value;
                       _tasks.RemoveFirst();
                   }

                   // Execute the task we pulled out of the queue
                   base.TryExecuteTask(item);
               }
           }
           // We're done processing items on the current thread
           finally { _currentThreadIsProcessingItems = false; }
       }, null);
   }

   // Attempts to execute the specified task on the current thread. 
   protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
   {
       // If this thread isn't already processing a task, we don't support inlining
       if (!_currentThreadIsProcessingItems) return false;

       // If the task was previously queued, remove it from the queue
       if (taskWasPreviouslyQueued) 
          // Try to run the task. 
          if (TryDequeue(task)) 
            return base.TryExecuteTask(task);
          else
             return false; 
       else 
          return base.TryExecuteTask(task);
   }

   // Attempt to remove a previously scheduled task from the scheduler. 
   protected sealed override bool TryDequeue(Task task)
   {
       lock (_tasks) return _tasks.Remove(task);
   }

   // Gets the maximum concurrency level supported by this scheduler. 
   public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } }

   // Gets an enumerable of the tasks currently scheduled on this scheduler. 
   protected sealed override IEnumerable<Task> GetScheduledTasks()
   {
       bool lockTaken = false;
       try
       {
           Monitor.TryEnter(_tasks, ref lockTaken);
           if (lockTaken) return _tasks;
           else throw new NotSupportedException();
       }
       finally
       {
           if (lockTaken) Monitor.Exit(_tasks);
       }
   }
}
// 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  
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  

此外, 代码库中还提供了几个示例任务计划程序:.NET Framework 4 的并行编程的示例In addition, several sample task schedulers are available on Code Gallery: Samples for Parallel Programming with the .NET Framework 4.

注解

TaskScheduler类的实例表示任务计划程序。An instance of the TaskScheduler class represents a task scheduler. 任务计划程序确保最终执行任务作业。A task scheduler ensures that the work of a task is eventually executed.

默认的任务计划程序基于 .NET Framework 4 线程池,提供工作窃取实现负载平衡、线程注入/退出以实现最大吞吐量,并提供整体卓越性能。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.

TaskScheduler类还可作为所有可自定义计划逻辑的扩展点。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 task scheduler and the thread pool

任务并行库和 PLINQ 的默认计划程序使用由ThreadPool类表示的 .NET Framework 线程池来排队和执行工作。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. 线程池使用由Task类型提供的信息来有效地支持并行任务和查询经常表示的细化并行 (生存期较短的工作单位)。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 global queue vs. local queues

线程池为每个应用程序域中的线程维护全局 FIFO (先进先出) 工作队列。The thread pool maintains a global FIFO (first-in, first-out) work queue for threads in each application domain. 每当程序调用ThreadPool.QueueUserWorkItem (或ThreadPool.UnsafeQueueUserWorkItem) 方法时, 工作就会置于此共享队列上, 并最终取消排队到下一个可用线程。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. 从 .NET Framework 4 开始, 此队列已进行了改进, 可使用类似ConcurrentQueue<T>于类的无锁算法。Starting with the .NET Framework 4, this queue has been improved to use a lock-free algorithm that resembles the ConcurrentQueue<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. 本地队列按后进先出 (LIFO) 顺序访问, 以保留缓存区域并减少争用情况。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. 并行 LINQ (PLINQ)Parallel类都广泛使用嵌套任务和子任务, 并通过使用本地工作队列实现重要的加速。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.

工作偷窃Work stealing

从 .NET Framework 4 开始, 线程池还具有工作窃取算法, 以帮助确保没有线程处于空闲状态, 而其他线程仍在队列中工作。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. 如果可以, 它将从尾部对工作项取消排队 (按照 FIFO 顺序)。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.

长时间运行的任务Long-running tasks

可能想要显式防止将任务放到本地队列上。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. 在这种情况下,可指定 System.Threading.Tasks.TaskCreationOptions 选项,它提示附加线程执行任务时可能需要计划程序,以使此任务不阻塞本地队列中其他线程或工作项的向前推动。In this case, you can specify the System.Threading.Tasks.TaskCreationOptions 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.

任务内联Task inlining

在某些情况下, Task当等待时, 它可能会在执行等待操作的线程上同步执行。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 reentrancy, task inlining only occurs when the wait target is found in the relevant thread's local queue.

指定同步上下文Specifying a synchronization context

可使用 TaskScheduler.FromCurrentSynchronizationContext 方法指定任务应计划在特定线程上运行。You can use the TaskScheduler.FromCurrentSynchronizationContext method to specify that a task should be scheduled to run on a particular thread. 在 Windows 窗体和 Windows Presentation Foundation 等框架中此操作非常有用,在此类框架中对用户界面对象的访问限制为只可访问在创建 UI 对象的同一线程上运行的代码。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.

下面的示例使用 Windows Presentation Foundation TaskScheduler.FromCurrentSynchronizationContext (WPF) 应用程序中的方法, 在创建用户界面 (UI) 控件的同一线程上计划任务。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. WPF 对象用于加载图像并调整图像大小。The WPF objects are used to load and resize the images. 然后将原始像素传递到一个任务, 该任务使用For循环将像素数据写入到大的单字节数组中。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. 然后, 将大数组传递给在 UI 线程上运行的任务, 在此任务中, 像素数据加载到图像控件中。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.

该示例将数据移出 UI 线程, 通过使用并行循环和Task对象对其进行修改, 然后将其传递回在 UI 线程上运行的任务。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. 当你必须使用任务并行库来执行 WPF API 不支持的操作或没有足够的速度时, 此方法非常有用。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. 在 WPF 中创建图像马赛克的另一种方法是使用System.Windows.Controls.WrapPanel控件并向其中添加图像。Another way to create an image mosaic in WPF is to use a System.Windows.Controls.WrapPanel control and add images to it. WrapPanel处理磁贴的定位工作。The WrapPanel handles the work of positioning the tiles. 但是, 只能在 UI 线程上执行此操作。However, this work can only be performed on the UI thread.

using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace WPF_CS1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private int fileCount;
        int colCount;
        int rowCount;
        private int tilePixelHeight;
        private int tilePixelWidth;
        private int largeImagePixelHeight;
        private int largeImagePixelWidth;
        private int largeImageStride;
        PixelFormat format;
        BitmapPalette palette = null;

        public MainWindow()
        {
            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);
            this.Width = largeImagePixelWidth + 40;
            image.Width = largeImagePixelWidth;
            image.Height = largeImagePixelHeight;


        }

        private void button_Click(object sender, RoutedEventArgs e)
        {

            // For best results use 1024 x 768 jpg files at 32bpp.
            string[] files = System.IO.Directory.GetFiles(@"C:\Users\Public\Pictures\Sample Pictures\", "*.jpg");

            fileCount = files.Length;
            Task<byte[]>[] images = new Task<byte[]>[fileCount];
            for (int i = 0; i < fileCount; i++)
            {
                int x = i;
                images[x] = Task.Factory.StartNew(() => LoadImage(files[x]));
            }

            // When they've all been loaded, tile them into a single byte array.
            var tiledImage = Task.Factory.ContinueWhenAll(
                images, (i) => 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 "image".
            var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();

            // On the UI thread, put the bytes into a bitmap and
            // display it in the Image control.
            var t3 = tiledImage.ContinueWith((antecedent) =>
            {
                // Get System DPI.
                Matrix m = PresentationSource.FromVisual(Application.Current.MainWindow)
                                            .CompositionTarget.TransformToDevice;
                double dpiX = m.M11;
                double dpiY = m.M22;

                BitmapSource bms = BitmapSource.Create(largeImagePixelWidth,
                    largeImagePixelHeight,
                    dpiX,
                    dpiY,
                    format,
                    palette, //use default palette
                    antecedent.Result,
                    largeImageStride);
                image.Source = bms;
            }, UISyncContext);
        }

        byte[] LoadImage(string filename)
        {
            // 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.

            BitmapImage bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.UriSource = new Uri(filename);
            bitmapImage.DecodePixelHeight = tilePixelHeight;
            bitmapImage.DecodePixelWidth = tilePixelWidth;
            bitmapImage.EndInit();

            format = bitmapImage.Format;
            int size = (int)(bitmapImage.Height * bitmapImage.Width);
            int stride = (int)bitmapImage.Width * 4;
            byte[] dest = new byte[stride * tilePixelHeight];

            bitmapImage.CopyPixels(dest, stride, 0);

            return dest;
        }

        int Stride(int pixelWidth, int bitsPerPixel)
        {
            return (((pixelWidth * bitsPerPixel + 31) / 32) * 4);
        }

        // 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.
        byte[] TileImages(Task<byte[]>[] sourceImages)
        {
            byte[] largeImage = new byte[largeImagePixelHeight * largeImageStride];
            int tileImageStride = tilePixelWidth * 4; // hard coded to 32bpp

            Random rand = new Random();
            Parallel.For(0, rowCount * colCount, (i) =>
            {
                // Pick one of the images at random for this tile.
                int cur = rand.Next(0, sourceImages.Length);
                byte[] pixels = sourceImages[cur].Result;

                // Get the starting index for this tile.
                int row = i / colCount;
                int col = (int)(i % colCount);
                int idx = ((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.
                int tileImageIndex = 0;
                for (int j = 0; j < tilePixelHeight; j++)
                {
                    // Write the next scanline for this tile.
                    for (int k = 0; k < tileImageStride; k++)
                    {
                        largeImage[idx++] = pixels[tileImageIndex++];
                    }
                    // Advance to the beginning of the next scanline.
                    idx += largeImageStride - tileImageStride;
                }
            });
            return largeImage;
        }
    }
}
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 have 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
        ' 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

若要创建此示例, 请在 Visual Studio 中创建一个 WPF 应用程序项目, 并将C#其命名为 WPF_CS1 (适用于 wpf 项目) 或 WPF_VB1 (适用 Visual Basic 于 wpf 项目)。To create the example, create a WPF application project in Visual Studio and name it WPF_CS1 (for a C# WPF project) or WPF_VB1 (for a Visual Basic WPF project). 然后执行以下操作:Then do the following:

  1. 在 "设计" 视图中Image , 将控件从 "工具箱" 拖动到设计图面的左上角。In design view, drag an Image control from the Toolbox onto the upper left corner of the design surface. 在 "属性" 窗口的 "名称" 文本框中, 将控件命名为 "image"。In the Name textbox of the Properties window, name the control "image".

  2. 将控件从 "工具箱" 拖动到应用程序窗口的左下部分。 ButtonDrag a Button control from the Toolbox to the lower left part of the application window. 在 XAML 视图中, 将Content按钮的属性指定为 "创建马赛克", 并将其Width属性指定为 "100"。In XAML view, specify the Content property of the button as "Make a mosaic", and specify its Width property as "100". 通过将添加<Button> button_ClickClick="button_Click"元素, 将事件与示例的代码Click中定义的事件处理程序连接。Connect the Click event with the button_Click event handler defined in the example's code by adding Click="button_Click" to the <Button> element. 在 "属性" 窗口的 "名称" 文本框中, 将控件命名为 "button"。In the Name textbox of the Properties window, name the control "button".

  3. 将 MainWindow.xaml.cs 或 Mainwindow.xaml 文件的全部内容替换为此示例中的代码。Replace the entire contents of the MainWindow.xaml.cs or MainWindow.xaml.vb file with the code from this example. 对于C# WPF 项目, 请确保工作区的名称与项目名称匹配。For a C# WPF project, make sure that the name of the workspace matches the project name.

  4. 该示例从名为 C:\Users\Public\Pictures\Sample\的目录中读取 JPEG 图像。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. 例如, 仅支持32位/像素的图像;在调整大小操作过程中, BitmapImage对象会损坏其他格式的图像。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.

构造函数

TaskScheduler()

初始化 TaskSchedulerInitializes the TaskScheduler.

属性

Current

获取与当前正在执行的任务关联的 TaskSchedulerGets the TaskScheduler associated with the currently executing task.

Default

获取由 .NET Framework 提供的默认 TaskScheduler 实例。Gets the default TaskScheduler instance that is provided by the .NET Framework.

Id

获取此 TaskScheduler 的唯一 ID。Gets the unique ID for this TaskScheduler.

MaximumConcurrencyLevel

指示此 TaskScheduler 能够支持的最大并发级别。Indicates the maximum concurrency level this TaskScheduler is able to support.

方法

Equals(Object)

确定指定的对象是否等于当前对象。Determines whether the specified object is equal to the current object.

(继承自 Object)
Finalize()

释放与此计划程序关联的所有资源。Frees all resources associated with this scheduler.

FromCurrentSynchronizationContext()

创建与当前 SynchronizationContext 关联的 TaskSchedulerCreates a TaskScheduler associated with the current SynchronizationContext.

GetHashCode()

作为默认哈希函数。Serves as the default hash function.

(继承自 Object)
GetScheduledTasks()

仅对于调试器支持,生成当前排队到计划程序中等待执行的 Task 实例的枚举。For debugger support only, generates an enumerable of Task instances currently queued to the scheduler waiting to be executed.

GetType()

获取当前实例的 TypeGets the Type of the current instance.

(继承自 Object)
MemberwiseClone()

创建当前 Object 的浅表副本。Creates a shallow copy of the current Object.

(继承自 Object)
QueueTask(Task)

Task 排队到计划程序中。Queues a Task to the scheduler.

ToString()

返回表示当前对象的字符串。Returns a string that represents the current object.

(继承自 Object)
TryDequeue(Task)

尝试将以前排队到此计划程序中的 Task 取消排队。Attempts to dequeue a Task that was previously queued to this scheduler.

TryExecuteTask(Task)

尝试在此计划程序上执行提供的 TaskAttempts to execute the provided Task on this scheduler.

TryExecuteTaskInline(Task, Boolean)

确定是否可以在此调用中同步执行提供的 Task,如果可以,将执行该任务。Determines whether the provided Task can be executed synchronously in this call, and if it can, executes it.

事件

UnobservedTaskException

出错的任务中未观察到的异常将触发异常呈报策略时出现,默认情况下会终止进程。Occurs when a faulted task's unobserved exception is about to trigger exception escalation policy, which, by default, would terminate the process.

适用于

线程安全性

抽象TaskScheduler类型的所有成员都是线程安全的, 可同时从多个线程使用。All members of the abstract TaskScheduler type are thread-safe and may be used from multiple threads concurrently.

另请参阅