Este artículo se tradujo automáticamente. Para ver el artículo en inglés, active la casilla Inglés. Además, puede mostrar el texto en inglés en una ventana emergente si mueve el puntero del mouse sobre el texto.
Traducción
Inglés

Clase TaskScheduler

 

Publicado: julio de 2016

Representa un objeto que administra el trabajo de bajo nivel de poner en cola tareas en los subprocesos.

Espacio de nombres:   System.Threading.Tasks
Ensamblado:  mscorlib (en mscorlib.dll)

System.Object
  System.Threading.Tasks.TaskScheduler

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

NombreDescripción
System_CAPS_protmethodTaskScheduler()

Inicializa el TaskScheduler.

NombreDescripción
System_CAPS_pubpropertySystem_CAPS_staticCurrent

Obtiene el TaskScheduler asociado a la tarea que se está ejecuta actualmente.

System_CAPS_pubpropertySystem_CAPS_staticDefault

Obtiene el valor predeterminado TaskScheduler instancia proporcionada por .NET Framework.

System_CAPS_pubpropertyId

Obtiene el identificador único para este TaskScheduler.

System_CAPS_pubpropertyMaximumConcurrencyLevel

Indica el nivel de simultaneidad máximo TaskScheduler es capaz de admitir.

NombreDescripción
System_CAPS_pubmethodEquals(Object)

Determina si el objeto especificado es igual al objeto actual.(Heredado de Object).

System_CAPS_protmethodFinalize()

Permite que un objeto intente liberar recursos y realizar otras operaciones de limpieza antes de ser reclamado por el recolector de basura. (Heredado de Object).

System_CAPS_pubmethodSystem_CAPS_staticFromCurrentSynchronizationContext()

Crea un TaskScheduler asociado con el System.Threading.SynchronizationContext actual.

System_CAPS_pubmethodGetHashCode()

Sirve como la función hash predeterminada.(Heredado de Object).

System_CAPS_protmethodGetScheduledTasks()

Depurador de compatibilidad sólo, genera un enumerable de Task instancias actualmente en la cola del programador esperando para ejecutarse.

System_CAPS_pubmethodGetType()

Obtiene el Type de la instancia actual.(Heredado de Object).

System_CAPS_protmethodMemberwiseClone()

Crea una copia superficial del Object actual.(Heredado de Object).

System_CAPS_protmethodQueueTask(Task)

Las colas de un Task al programador.

System_CAPS_pubmethodToString()

Devuelve una cadena que representa al objeto actual. (Heredado de Object).

System_CAPS_protmethodTryDequeue(Task)

Intenta quitar un Task que anteriormente estaba en la cola de este programador.

System_CAPS_protmethodTryExecuteTask(Task)

Intenta ejecutar proporcionado Task en este programador.

System_CAPS_protmethodTryExecuteTaskInline(Task, Boolean)

Determina si el Task puede ejecutarse sincrónicamente en esta llamada y si es posible, se ejecuta.

NombreDescripción
System_CAPS_pubeventSystem_CAPS_staticUnobservedTaskException

Se produce cuando la excepción no observada de un error de la tarea está a punto de desencadenar la directiva de escalado de excepción que, de forma predeterminada, finalizaría el proceso.

Una instancia de la TaskScheduler clase representa un programador de tareas. Un programador de tareas asegura que se ejecuta el trabajo de una tarea.

El programador de tareas predeterminado está basado en el grupo de subprocesos .NET Framework 4, que proporciona robo de trabajo para el equilibrio de carga, inyección/retirada de subprocesos, a fin de obtener el máximo resultado y un buen rendimiento en general. Debería ser suficiente para la mayoría de los escenarios.

La TaskScheduler clase también actúa como el punto de extensión para toda la lógica de programación personalizable. Esto incluye mecanismos como cómo programar una tarea para su ejecución y las tareas programadas cómo debe mostrarse a depuradores. Si necesita funcionalidad especial, puede crear a un programador personalizado y habilitarlo para tareas o consultas concretas.

En este tema:

el programador de tareas predeterminado y el grupo de subprocesos

la cola global frente a las colas locales
robo de trabajo
tareas de larga duración
inclusión de tareas

especificar un contexto de sincronización                         

El programador predeterminado para el Task Parallel Library y PLINQ utiliza el grupo de subprocesos de .NET Framework, que se representa mediante el ThreadPool (clase), poner en cola y ejecutar el trabajo. El grupo de subprocesos utiliza la información proporcionada por el Task tipo para admitir el paralelismo específico (unidades efímeras de trabajo) que las tareas y consultas paralelas a menudo representan.

El grupo de subprocesos mantiene la cola de subprocesos en cada dominio de aplicación de trabajo de un global FIFO (primero en salir). Cuando un programa llama la ThreadPool.QueueUserWorkItem (o ThreadPool.UnsafeQueueUserWorkItem) método, el trabajo es poner en esta cola compartida y finalmente sale de la cola hacia el subproceso siguiente que está disponible. A partir de .NET Framework 4, esta cola se ha mejorado para usar un algoritmo sin bloqueo que se parece a la ConcurrentQueue<T> clase. Mediante esta implementación sin bloqueo, el grupo de subprocesos emplea menos tiempo en poner y sacar elementos de trabajo de las colas. Esta ventaja de rendimiento está disponible para todos los programas que usan el grupo de subprocesos.

Las tareas de nivel superior, que son tareas que no se crean en el contexto de otra tarea, se colocan en la cola global igual que cualquier otro elemento de trabajo. Sin embargo, las tareas anidadas o secundarias, que se crean en el contexto de otra tarea, se controlan de forma bastante distinta. Una tarea secundaria o anidada se coloca en una cola local que es específica del subproceso en el que la tarea primaria se está ejecutando. La tarea primaria puede ser una tarea de nivel superior o también puede ser el elemento secundario de otra tarea. Cuando este subproceso está listo para más trabajo, primero busca en la cola local. Si hay elementos de trabajo esperando, se puede tener acceso a ellos rápidamente. Se tiene acceso a las colas locales en el orden último en salir (LIFO) para conservar el emplazamiento en caché y reducir la contención. Para obtener más información sobre las tareas secundarias y anidadas, vea Attached and Detached Child Tasks.

El uso de colas locales no solo reduce la presión en la cola global, pero que también aprovecha las ventajas de la situación de los datos. Cola de elementos de trabajo en el equipo local con frecuencia las estructuras de datos de referencia que están físicamente cerca entre sí en la memoria. En estos casos, los datos ya están en la memoria caché después de la primera tarea se ha ejecutado y se puede obtener acceso rápidamente. Ambos Parallel LINQ (PLINQ) y Parallel clase usa tareas anidadas y tareas secundarias ampliamente y conseguir aumentos significativos de velocidad utilizando las colas de trabajo locales.

A partir de .NET Framework 4, el grupo de subprocesos también incluye un algoritmo de robo de trabajo para ayudar a asegurar que ningún subproceso esté inactivo mientras otros todavía tienen trabajo en sus colas. Cuando un subproceso ThreadPool está listo para más trabajo, examina primero el encabezado de la cola local, a continuación, en la cola global y después en las colas locales de otros subprocesos. Si encuentra un elemento de trabajo en la cola local de otro subproceso, aplica primero heurística para asegurarse de que puede ejecutar el trabajo eficazmente. Si es posible, quita de la cola el elemento de trabajo de la cola (en orden FIFO). Esto reduce la contención en cada cola local y mantiene la situación de los datos. Esta arquitectura permite el equilibrio de carga de grupo de subprocesos trabajar más eficazmente que en versiones anteriores.

Tal vez le interese evitar explícitamente que una tarea se coloque en una cola local. Por ejemplo, puede saber que un elemento de trabajo determinado se ejecutará durante un tiempo relativamente largo y es probable que bloquee el resto de los elementos de trabajo de la cola local. En este caso, puede especificar la opción TaskCreationOptions.LongRunning, que proporciona una sugerencia al programador que le indica que tal vez es necesario un subproceso adicional para que la tarea no bloquee el progreso de otros subprocesos o elementos de trabajo de la cola local. Mediante esta opción no el grupo de subprocesos por completo, incluidas las colas globales y locales.

En algunos casos, cuando un Task se espera, se puede ejecutar sincrónicamente en el subproceso que realiza la operación de espera. Esto mejora el rendimiento evitando la necesidad de un subproceso adicional y usar en su lugar, el subproceso existente que se habría bloqueado en caso contrario. Para evitar errores de volver a entrar, inclusión de tareas solo se produce cuando el destino de la espera se encuentra en la cola local del subproceso pertinente.

Puede utilizar el método TaskScheduler.FromCurrentSynchronizationContext para especificar que una tarea se debería programar para ejecutarse en un subproceso determinado. Esto es útil en marcos como Windows Forms y Windows Presentation Foundation, donde el acceso a los objetos de interfaz de usuario está restringido a menudo para el código que se está ejecutando en el mismo subproceso en el que se creó el objeto UI. Para obtener más información, vea Cómo: programar trabajo en el subproceso de interfaz de usuario (UI).

En el ejemplo siguiente se usa el TaskScheduler.FromCurrentSynchronizationContext método en una aplicación de Windows Presentation Foundation (WPF) para programar una tarea en el mismo subproceso donde se creó el control de interfaz de usuario. En el ejemplo se crea un mosaico de imágenes que se seleccionan aleatoriamente de un directorio especificado. Los objetos WPF se utilizan para cargar y cambiar el tamaño de las imágenes. Los píxeles sin formato, a continuación, se pasan a una tarea que usa un For bucle para escribir los datos de píxeles en una matriz grande de un solo byte. Se necesita ninguna sincronización porque no hay dos mosaicos ocupa los mismos elementos de matriz. Los iconos también pueden escribirse en cualquier orden porque su posición se calcula independientemente de cualquier otro icono. La matriz de gran tamaño, a continuación, se pasa a una tarea que se ejecuta en el subproceso de interfaz de usuario, que se cargan los datos de píxeles en un control de imagen.

El ejemplo mueve los datos desde el subproceso de interfaz de usuario, se modifica mediante el uso de bucles paralelos y Task objetos y, a continuación, se pasa a una tarea que se ejecuta en el subproceso de interfaz de usuario. Este enfoque es útil cuando deba utilizar Task Parallel Library para realizar operaciones que no son compatibles con la API de WPF, o que no son suficientemente rápidos. Otra manera de crear un mosaico de la imagen en WPF es usar un System.Windows.Controls.WrapPanel controlar y agregar imágenes a él. El WrapPanel administra el trabajo de colocar los mosaicos. Sin embargo, este trabajo solo puede realizarse en el subproceso de interfaz de usuario.

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

Para crear el ejemplo, crear un proyecto de aplicación de WPF en Visual Studio y asígnele un nombre de su elección. A continuación, haga lo siguiente:

  1. En la vista de diseño, arrastre un Image controlar desde la cuadro de herramientas a la superficie de diseño. En la vista XAML, especificar la alineación horizontal como "Left". El tamaño no importa porque se redimensiona el control de forma dinámica en tiempo de ejecución. Acepte el nombre predeterminado, "image".

  2. Arrastre un Button controlar desde la cuadro de herramientas en la parte inferior izquierda de la ventana de la aplicación. Haga doble clic en el botón para agregar una Click controlador de eventos. En la vista XAML, especifique la Content propiedad del botón como "Hacer que un mosaico" y especifica la alineación horizontal como "Left". Acepte el nombre predeterminado, "button".

  3. Reemplace todo el contenido del archivo MainWindow.xaml.cs o MainWindow.xaml.vb por el código de este ejemplo. Asegúrese de que el nombre del área de trabajo coincide con el nombre del proyecto.

  4. En el ejemplo se lee las imágenes JPEG de un directorio denominado C:\Users\Public\Pictures\Sample pictures\ con. Crear el directorio y coloque algunas imágenes en ella o cambiar la ruta de acceso para hacer referencia a cualquier otro directorio que contiene imágenes.

Este ejemplo tiene algunas limitaciones. Por ejemplo, se admiten imágenes de solo 32-bits por píxel; imágenes en otros formatos están dañadas por la BitmapImage objeto durante la operación de cambio de tamaño. Además, las imágenes de origen deben mayores que el tamaño del mosaico. Como un ejercicio más extenso, puede agregar funcionalidad para controlar varios formatos de píxel y tamaños de archivo.

En el siguiente ejemplo se toma de la Samples for Parallel Programming with the .NET Framework 4 en el sitio Web de MSDN Code Gallery. Crea a un programador de tareas personalizado que limita el número de subprocesos utilizados por la aplicación. A continuación, inicia dos conjuntos de tareas y muestra información acerca de la tarea y el subproceso en el que se ejecuta la tarea.

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.

Plataforma universal de Windows
Disponible desde 8
.NET Framework
Disponible desde 4.0
Biblioteca de clases portable
Se admite en: plataformas portátiles de .NET
Silverlight
Disponible desde 5.0
Windows Phone Silverlight
Disponible desde 8.0
Windows Phone
Disponible desde 8.1

Todos los miembros de la abstracta TaskScheduler tipo son seguros para subprocesos y se pueden usar desde varios subprocesos simultáneamente.

Volver al principio
Mostrar: