Данная статья переведена с помощью средств машинного перевода. Чтобы просмотреть ее на английском языке, установите флажок Английский. Вы также можете просматривать английский текст во всплывающем окне, наводя указатель мыши на переведенный текст.
Перевод
Английский

Класс TaskScheduler

 

Опубликовано: Июль 2016

Представляет объект, обрабатывающий низкоуровневую постановку задач в очередь на потоки.

Пространство имен:   System.Threading.Tasks
Сборка:  mscorlib (в mscorlib.dll)

System.Object
  System.Threading.Tasks.TaskScheduler

[HostProtectionAttribute(SecurityAction.LinkDemand, Synchronization = true, 
	ExternalThreading = true)]
[PermissionSetAttribute(SecurityAction.InheritanceDemand, Unrestricted = true)]
public abstract class TaskScheduler

ИмяОписание
System_CAPS_protmethodTaskScheduler()

Инициализирует объект TaskScheduler.

ИмяОписание
System_CAPS_pubpropertySystem_CAPS_staticCurrent

Возвращает TaskScheduler связанных с текущей выполняемой задачей.

System_CAPS_pubpropertySystem_CAPS_staticDefault

Возвращает значение по умолчанию TaskScheduler предоставляемый платформой .NET Framework.

System_CAPS_pubpropertyId

Получает уникальный идентификатор для этого TaskScheduler.

System_CAPS_pubpropertyMaximumConcurrencyLevel

Это указывает максимальный уровень параллелизма TaskScheduler способен поддерживать.

ИмяОписание
System_CAPS_pubmethodEquals(Object)

Определяет, равен ли заданный объект текущему объекту.(Наследуется от Object.)

System_CAPS_protmethodFinalize()

Позволяет объекту попытаться освободить ресурсы и выполнить другие операции очистки, перед тем как он будет уничтожен во время сборки мусора.(Наследуется от Object.)

System_CAPS_pubmethodSystem_CAPS_staticFromCurrentSynchronizationContext()

Создает TaskScheduler для связывания с текущим элементом System.Threading.SynchronizationContext.

System_CAPS_pubmethodGetHashCode()

Служит хэш-функцией по умолчанию.(Наследуется от Object.)

System_CAPS_protmethodGetScheduledTasks()

Для отладчика поддержки, создает только перечислимые Task экземпляры в настоящее время в очереди планировщика, ожидающих выполнения.

System_CAPS_pubmethodGetType()

Возвращает объект Type для текущего экземпляра.(Наследуется от Object.)

System_CAPS_protmethodMemberwiseClone()

Создает неполную копию текущего объекта Object.(Наследуется от Object.)

System_CAPS_protmethodQueueTask(Task)

Очереди Task планировщику.

System_CAPS_pubmethodToString()

Возвращает строку, представляющую текущий объект.(Наследуется от Object.)

System_CAPS_protmethodTryDequeue(Task)

Пытается удалить из очереди Task был ранее в очереди данного планировщика.

System_CAPS_protmethodTryExecuteTask(Task)

Предпринимает попытку выполнить предоставленный Task на данном планировщике.

System_CAPS_protmethodTryExecuteTaskInline(Task, Boolean)

Определяет ли предоставленный Task может быть выполнена синхронно в этот вызов и если это возможно, он выполняется.

ИмяОписание
System_CAPS_pubeventSystem_CAPS_staticUnobservedTaskException

Создается при активации политики эскалации исключений из-за непредвиденного исключения задачи, завершившейся сбоем. По умолчанию из-за этой политики процесс будет прерван.

Экземпляр TaskScheduler класс представляет планировщика задач. Планировщик задач гарантирует, что работа над задачей в итоге будет выполнена.

Планировщик заданий по умолчанию основан на пуле потоков .NET Framework 4, который обеспечивает перехват работы для балансировки нагрузки, вставку/удаление потока для максимальной пропускной способности и общее повышение производительности. Его должно быть достаточно для большинства сценариев.

TaskScheduler Класс также служит точка расширения для всех настраиваемых логики планирования. Это включает механизмы, такие как отладчики предоставления способ планирования задачи для выполнения и запланированных задач. Если требуются специальные функциональные возможности, можно создать пользовательский планировщик и включить его для определенных задач или запросов.

В этом разделе:

планировщик задач по умолчанию и пула потоков

глобальную очередь и локальные очереди
переноса
длительно выполняемых задач
Встраивание задачи

Указание контекста синхронизации                         

Планировщик по умолчанию для библиотеки параллельных задач и PLINQ использует пул потоков .NET Framework, которое представляется ThreadPool класса, чтобы очередь и выполнения работы. Пул потоков использует сведения, предоставляемые Task тип для эффективной поддержки точного параллелизма (кратковременных единиц работы), часто представляют параллельные задачи и запросы.

Пул потоков обслуживает очередь глобальные FIFO (первым поступил — первым обслужен) для потоков в каждом домене приложения. Каждый раз, когда программа вызывает ThreadPool.QueueUserWorkItem (или ThreadPool.UnsafeQueueUserWorkItem) метода, работа поместить в этой общей очереди и в конечном итоге отменяется помещенных в очередь в следующий поток, который становится доступным. Начиная с .NET Framework 4, эта очередь была улучшена для использования алгоритма без блокировки, похожа на ConcurrentQueue<T> класса. Используя эту свободную от блокировок реализацию, пул потоков требуется меньше времени при его построении очереди и рабочие элементы. Такое преимущество в производительности доступно для всех программ, использующих пул потоков.

Задачи верхнего уровня, которые являются задачами, не созданными в контексте других задач, помещаются в глобальную очередь так же, как и другие рабочие элементы. Однако вложенные или дочерние задачи, создаваемые в контексте других задач, обрабатываются по-другому. Дочерняя или вложенная задача помещается в локальную очередь, относящуюся к потоку, в котором выполняется родительская задача. Родительская задача может быть задачей верхнего уровня или дочерней задачей другой задачи. Когда этот поток готов для дополнительной работы, сначала он выполняет поиск в локальной очереди. Если в ней существует ожидающие рабочие элементы, к ним возможен быстрый доступ. Локальные очереди осуществляется в последним поступил — первым обслужен (LIFO) для сохранения локальности кэша и снизить вероятность возникновения конфликтов. Дополнительные сведения о дочерних и вложенных задачах см. в разделе Attached and Detached Child Tasks.

Использование локальных очередей не только снижает нагрузку на глобальную очередь, но также использует преимущества локальности данных. Рабочие элементы в локальной очереди часто ссылаются на структуры данных, находящихся рядом друг с другом в памяти. В этих случаях данные уже находятся в кэше после выполнения первой задачи был выполнен и возможен быстрый. Оба Parallel LINQ (PLINQ) и Parallel класса широко используют вложенные и дочерние задачи и получают значительное увеличение скорости с помощью локальных рабочих очередей.

Начиная с .NET Framework 4, пул потоков также отражает алгоритм перехвата работы, чтобы убедиться, что одни потоки простаивают простоя во время в очередях других потоков имеется работа. Когда поток из пула потоков готов для дополнительной работы, сначала он выполняет поиск в своей локальной очереди, далее в глобальной очереди, а затем в локальных очередях других потоков. При обнаружении рабочего элемента в локальной очереди другого потока он сначала применяет эвристику, чтобы убедиться, что он может эффективно выполнить эту работу. Если это возможно, он выводит из очереди рабочий элемент с конца (в порядке FIFO). Это уменьшает конкуренцию внутри каждой из локальных очередей и сохраняет локальность данных. Эта архитектура позволяет потоков пула-Балансировка нагрузки работает более эффективно, чем в предыдущих версиях.

Может потребоваться явно запретить помещение задачи в локальную очередь. Например, вы знаете, что определенный рабочий элемент будет выполняться довольно долго и может заблокировать другие рабочие элементы в локальной очереди. В таком случае можно указать параметр TaskCreationOptions.LongRunning, который подсказывает планировщику, что для задачи может потребоваться дополнительный поток, чтобы она не блокировала дальнейший ход работы других потоков или рабочих элементов в локальной очереди. При использовании этого параметра позволяет избежать пул потоков полностью, включая глобальные и локальные очереди.

В некоторых случаях, когда Task ожидает, она может быть выполнена синхронно в потоке, который выполняет операцию ожидания. Это повышает производительность, устраняет необходимость в дополнительном потоке и вместо использования существующего потока, который в противном случае может быть заблокирован. Во избежание ошибок из-за повторного входа Встраивание задачи происходит только при обнаружении цели ожидания, в локальной очереди соответствующего потока.

С помощью метода TaskScheduler.FromCurrentSynchronizationContext можно указать, что задачу необходимо планировать для запуска в определенном потоке. Это полезно на платформах, например Windows Forms и Windows Presentation Foundation, где доступ к объектам пользовательского интерфейса часто ограничен кодом, выполняемым в том же потоке, в котором был создан этот объект пользовательского интерфейса. Дополнительные сведения см. в разделе как: Планирование работы в потоке пользовательского интерфейса (UI).

В следующем примере используется TaskScheduler.FromCurrentSynchronizationContext метода в приложении Windows Presentation Foundation (WPF) для планирования задачи, в том же потоке, созданный пользовательский элемент управления пользовательского интерфейса. В примере создается мозаика из изображений, которые выбираются случайным образом из указанного каталога. Для загрузки и изменения размеров изображения используются объекты WPF. Необработанные пиксели передаются задаче, которая использует For цикле для записи в большой массив однобайтовых точек данных. Синхронизация не требуется, так как нет две плитки занимать одинаковые элементы массива. Плитки также могут записываться в любом порядке, так как их расположение рассчитывается независимо друг от друга. Затем большой массив передается задачу, которая выполняется в потоке пользовательского интерфейса, где точки данных загружается в элемент управления Image.

Пример перемещает данные из потока пользовательского интерфейса, изменяет его с помощью параллельных циклов и Task объектов, а затем передает его задачу, которая выполняется в потоке пользовательского интерфейса. Этот подход полезен, если необходимо использовать библиотеку параллельных задач для выполнения операций, которые не поддерживаются WPF API или работают недостаточно быстро. Другой способ создания мозаики из изображения в WPF является использование System.Windows.Controls.WrapPanel управления и добавление изображений в него. WrapPanel Выполняет работу по расположению элементов мозаики. Однако эта работа может выполняться только в потоке пользовательского интерфейса.

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
            // 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;
        }
    }
}

Для создания примера, создавать проект приложения WPF в Visual Studio и назначьте ему имя по своему усмотрению. Затем выполните следующие действия.

  1. В представлении конструктора перетащите Image управления из элементов в область конструктора. В представлении XAML укажите горизонтального выравнивания как «Левый». Размер не имеет значения, так как элемент управления изменяется динамически во время выполнения. Примите имя по умолчанию, «изображение».

  2. Перетащите Button управления из элементов в левой нижней части окна приложения. Дважды щелкните кнопку, чтобы добавить Click обработчика событий. В представлении XAML задайте Content свойства кнопки, как «Обеспечить мозаики» и задайте значение горизонтального выравнивания как «Левый». Примите имя по умолчанию, «button».

  3. Замените все содержимое файла MainWindow.xaml.cs или MainWindow.xaml.vb код из этого примера. Убедитесь, что имя рабочей области, совпадает с именем проекта.

  4. В примере считывается JPEG-изображения из каталога с именем C:\Users\Public\Pictures\Sample Pictures\. Создать каталог и поместите в него некоторые рисунки либо измените путь, чтобы ссылаться на другие каталоги, содержащее изображения.

В этом примере имеет некоторые ограничения. Например поддерживаются только 32-бит на точку изображений; изображения в других форматах повреждены BitmapImage объекта во время операции изменения размера. Кроме того исходные изображения должны задаваться больше, чем размер плитки. В качестве дополнительного упражнения можно добавить возможности по обработке различных форматов пикселей и размер файла.

Следующий пример взят из Samples for Parallel Programming with the .NET Framework 4 в коллекции кода MSDN веб-сайта. Он создает пользовательский планировщик задач, ограничивающая количество потоков, используемых в приложении. Затем он запускает два набора задач и отображает сведения о задаче и потоке, на котором выполняется задача.

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  

In addition, several sample task schedulers are available on Code Gallery: Samples for Parallel Programming with the .NET Framework 4http://go.microsoft.com/fwlink/?LinkID=165717.

Универсальная платформа Windows
Доступно с 8
.NET Framework
Доступно с 4.0
Переносимая библиотека классов
Поддерживается в: переносимые платформы .NET
Silverlight
Доступно с 5.0
Windows Phone Silverlight
Доступно с 8.0
Windows Phone
Доступно с 8.1

Все члены абстрактного TaskScheduler типа являются потокобезопасными и может использоваться несколькими потоками одновременно.

Вернуться в начало
Показ: