Compartir a través de


Programadores de tareas

Los programadores de tarea se representan mediante la clase System.Threading.Tasks.TaskScheduler. Un programador de tareas se asegura de que se ejecuta el trabajo de una tarea. El programador de tareas predeterminado está basado en .NET Framework 4 ThreadPool, que proporciona robo de trabajo para el equilibrio de carga, inyección/retirada de subprocesos para obtener el máximo resultado y un buen rendimiento en general. Debería ser suficiente para la mayoría de los escenarios. Sin embargo, si necesita funcionalidad especial, puede crear un programador personalizado y habilitarlo para tareas o consultas concretas. Para obtener más información sobre cómo crear y usar un programador de tareas personalizado, vea Cómo: Crear un programador de tareas que limita el grado de simultaneidad. Para obtener ejemplos adicionales de programadores personalizados, vea Parallel Extensions Samples en el sitio web Galería de código MSDN.

Programador de tareas predeterminado y ThreadPool

El programador predeterminado para Task Parallel Library y PLINQ utiliza .NET Framework ThreadPool para poner en cola y ejecutar el trabajo. En .NET Framework 4, ThreadPool utiliza la información que proporciona el tipo System.Threading.Tasks.Task para admitir el paralelismo específico (unidades efímeras de trabajo) que las tareas y consultas paralelas representan a menudo.

Cola global ThreadPool frente acolas locales

Como en versiones anteriores de .NET Framework, ThreadPool mantiene una cola de trabajo FIFO (primero en llegar, primero en salir) global para los subprocesos en cada dominio de aplicación. Cuando un programa llama a QueueUserWorkItem (o UnsafeQueueUserWorkItem), el trabajo se coloca en esta cola compartida y finalmente sale de la cola hacia el subproceso siguiente que está disponible. En .NET Framework 4, esta cola se ha mejorado para usar un algoritmo sin bloqueo que se parece a la clase ConcurrentQueue. Mediante esta implementación sin bloqueo, ThreadPool emplea menos tiempo en poner y sacar de las colas los elementos de trabajo. Esta ventaja de rendimiento está disponible para todos los programas que utilizan ThreadPool.

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 entrar (LIFO), primero en salir con el fin de conservar la situación de la memoria caché y reducir la contención. Para obtener más información sobre las tareas secundarias y anidadas, vea Tareas anidadas y tareas secundarias.

En el siguiente ejemplo se muestran algunas tareas que se programan en la cola global y otras que se programan en la cola local.

Sub QueueTasks()

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

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

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

                                          taskB.Start()
                                          taskC.Start()

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

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

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

    });
}

El uso de colas locales reduce no solo la presión en la cola global, también aprovecha la situación de los datos. Los elementos de trabajo de la cola local con frecuencia hacen referencia a estructuras de datos que están físicamente cerca unos de otros en memoria. En estos casos, los datos ya están en la memoria caché después de que la primera tarea se haya ejecutado, y se puede obtener acceso rápidamente. LINQ Paralelo (PLINQ) y la clase Parallel usa tareas anidadas y tareas secundarias extensivamente y logran aumentos significativos de velocidad utilizando las colas de trabajo locales.

Robo de trabajo

.NET Framework 4ThreadPool también representa 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 puede, quita 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 ayuda a que el equilibrio de carga de ThreadPool en .NET Framework 4 trabaje más eficazmente que las versiones anteriores.

Tareas de ejecución prolongada

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 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. Utilizando esta opción, se evita ThreadPool completamente, incluidas las colas global y locales.

Inclusión de tareas

En algunos casos, cuando se espera un tarea, se puede ejecutar sincrónicamente en el subproceso que está realizando la operación de espera. Esto mejora el rendimiento, porque evita la necesidad de un subproceso adicional mediante el uso del subproceso existente que, de otro modo, se habría bloqueado. Para evitar errores después de volver a entrar, la inclusión de tareas solo tiene lugar cuando el destino de la espera se encuentra en la cola local del subproceso pertinente.

Especificar un contexto de sincronización

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 el trabajo en un contexto de sincronización especificado.

Vea también

Referencia

TaskScheduler

Conceptos

Task Parallel Library

Cómo: Programar el trabajo en un contexto de sincronización especificado

Otros recursos

Cómo: Crear un programador de tareas que limita el grado de simultaneidad

Generadores de tareas