Grupo de subprocesos administrados

Actualización: noviembre 2007

La clase ThreadPool proporciona a la aplicación un grupo de subprocesos de trabajo administrados por el sistema, que le permite concentrarse en tareas de aplicación en lugar de en la administración de los subprocesos. Si tiene tareas cortas que requieren el proceso de segundo plano, el grupo de subprocesos administrado es una manera fácil de aprovechar los diversos subprocesos.

Nota:

A partir de .NET Framework versión 2.0 Service Pack 1, el rendimiento del grupo de subprocesos ha mejorado significativamente en tres áreas clave que se identificaron como cuellos de botella en versiones anteriores de .NET Framework: poner tareas en la cola, enviar subprocesos del grupo de subprocesos y enviar subprocesos de finalización de E/S. Para usar esta funcionalidad, el destino de la aplicación debe ser .NET Framework versión 3.5. Para obtener más información, vea Arquitectura de .NET Framework 3.5.

Para las tareas de segundo plano que interactúan con la interfaz de usuario, la versión 2.0 de .NET Framework también proporciona la clase BackgroundWorker, que se comunica mediante eventos iniciados en el subproceso de la interfaz de usuario.

.NET Framework utiliza subprocesos ThreadPool para muchos fines, incluida la finalización de E/S asincrónica, devolución de llamadas a temporizadores, operaciones de espera registradas, llamadas asincrónicas a métodos utilizando delegados y conexiones de socket System.Net.

Cuándo no utilizar los subprocesos ThreadPool

Hay varios escenarios en los que es adecuado crear y administrar sus propios subprocesos en lugar de utilizar subprocesos ThreadPool:

  • Requiere tener un subproceso de primer plano.

  • Requiere que un subproceso que tenga una prioridad determinada.

  • Hay tareas que hacen que el subproceso se bloquee durante los períodos de tiempo prolongados. El grupo de subprocesos tiene un número máximo de subprocesos, por lo que un número grande de subprocesos ThreadPool bloqueados podría impedir que se iniciaran las tareas.

  • Es necesario colocar los subprocesos en un apartamento de un único subproceso. Todos los subprocesos ThreadPool están en el apartamento multiproceso.

  • Necesita tener una identidad estable asociada al subproceso o dedicar un subproceso a una tarea.

Características de los grupos de subprocesos

Los subprocesos ThreadPool son subprocesos de fondo. Vea Subprocesos de primer y segundo plano. Cada subproceso utiliza el tamaño de pila predeterminado, se ejecuta con la prioridad predeterminada y está en el apartamento multiproceso.

Sólo hay un grupo de subprocesos por cada proceso.

Excepciones en los subprocesos ThreadPool

Las excepciones no controladas producidas en subprocesos ThreadPool finalizan el proceso. Hay tres excepciones a esta regla:

  • En un subproceso ThreadPool se produce una excepción ThreadAbortException porque se llamó a Abort.

  • Se produce una excepción AppDomainUnloadedException en un subproceso ThreadPool, porque se descarga el dominio de aplicación.

  • Common Language Runtime o un proceso de host finaliza el subproceso.

Para obtener más información, vea Excepciones en subprocesos administrados.

Nota:

En las versiones 1.0 y 1.1 de .NET Framework, Common Language Runtime intercepta silenciosamente las excepciones no controladas en subprocesos ThreadPool. Esto podría dañar el estado de la aplicación y hacer que en el futuro las aplicaciones dejen de responder, lo que podría ser muy difícil de depurar.

Número máximo de subprocesos ThreadPool

El número de operaciones que pueden situarse en cola del grupo de subprocesos sólo está limitado por la memoria disponible; sin embargo, el grupo de subprocesos limita el número de subprocesos que pueden estar activos simultáneamente en el mismo proceso. De forma predeterminada, el límite es de 25 subprocesos de trabajo por CPU y 1.000 subprocesos de E/S de finalización.

Puede controlar el número máximo de subprocesos utilizando los métodos GetMaxThreads y SetMaxThreads.

Nota:

En las versiones 1.0 y 1.1 de .NET Framework, el tamaño del grupo de subprocesos no se puede establecer desde código administrado. El código que aloja Common Language Runtime puede establecer el tamaño mediante CorSetMaxThreads, definido en mscoree.h.

Número mínimo de subprocesos inactivos

El grupo de subprocesos también mantiene un número mínimo de subprocesos disponibles, incluso cuando todos los subprocesos están inactivos, para que las tareas situadas en cola se puedan iniciar inmediatamente. Los subprocesos inactivos que superen este mínimo se terminan para ahorrar recursos del sistema. De forma predeterminada, se mantiene un subproceso inactivo por cada procesador.

El grupo de subprocesos tiene un retardo integrado (de medio segundo en la versión 2.0 de .NET Framework) antes de iniciar los nuevos subprocesos inactivos. Si su aplicación inicia periódicamente muchas tareas en un breve plazo de tiempo, un pequeño aumento del número de subprocesos inactivos puede producir un aumento significativo en el rendimiento. Al establecer el número de subprocesos inactivos demasiado alto, se utilizan inútilmente recursos del sistema.

Puede controlar el número de subprocesos inactivos mantenido por el grupo de subprocesos utilizando los métodos GetMinThreads y SetMinThreads.

Nota:

En la versión 1.0 de .NET Framework no se puede establecer el número mínimo de subprocesos inactivos.

Omitir las comprobaciones de seguridad

El grupo de subprocesos también proporciona los métodos ThreadPool.UnsafeQueueUserWorkItem y ThreadPool.UnsafeRegisterWaitForSingleObject. Utilice estos métodos sólo cuando tenga la seguridad de que la pila de llamada no es relevante para ninguna comprobación de seguridad realizada durante la ejecución de la tarea situada en cola. QueueUserWorkItemy RegisterWaitForSingleObject interceptan la pila de llamada, que se combina en la pila del subproceso ThreadPool cuando el subproceso comienza a ejecutar una tarea. Si es necesario realizar una comprobación de seguridad, debe comprobarse toda la pila. Aunque la comprobación proporciona seguridad, es a costa del rendimiento.

Utilizar el grupo de subprocesos

El grupo de subprocesos se utiliza mediante una llamada a ThreadPool.QueueUserWorkItem desde código administrado (o a CorQueueUserWorkItem desde código no administrado) y pasando un delegado WaitCallback que representa el método que realiza la tarea. También puede poner en la cola los elementos de trabajo relacionados con una operación de espera con el método ThreadPool.RegisterWaitForSingleObject y pasando un WaitHandle que, cuando se le señala o se agota el tiempo de espera, inicia una llamada al método representado por el delegado WaitOrTimerCallback. En ambos casos, el grupo de subprocesos utiliza un subproceso de fondo para invocar al método de devolución de llamada.

Ejemplos de ThreadPool

En los tres ejemplos de código siguientes se muestran los métodos QueueUserWorkItem y RegisterWaitForSingleObject.

En el primer ejemplo se pone en cola una tarea muy sencilla, representada por el método ThreadProc, utilizando el método QueueUserWorkItem.

Imports System
Imports System.Threading

Public Class Example
    Public Shared Sub Main()
        ' Queue the task.
        ThreadPool.QueueUserWorkItem( _
            New WaitCallback(AddressOf ThreadProc))
        
        Console.WriteLine("Main thread does some work, then sleeps.")
        ' If you comment out the Sleep, the main thread exits before
        ' the thread pool task runs.  The thread pool uses background
        ' threads, which do not keep the application running.  (This
        ' is a simple example of a race condition.)
        Thread.Sleep(1000)

        Console.WriteLine("Main thread exits.")
    End Sub

    ' This thread procedure performs the task.
    Shared Sub ThreadProc(stateInfo As Object)
        ' No state object was passed to QueueUserWorkItem, so 
        ' stateInfo is null.
        Console.WriteLine("Hello from the thread pool.")
    End Sub
End Class
using System;
using System.Threading;
public class Example {
    public static void Main() {
        // Queue the task.
        ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
        
        Console.WriteLine("Main thread does some work, then sleeps.");
        // If you comment out the Sleep, the main thread exits before
        // the thread pool task runs.  The thread pool uses background
        // threads, which do not keep the application running.  (This
        // is a simple example of a race condition.)
        Thread.Sleep(1000);

        Console.WriteLine("Main thread exits.");
    }

    // This thread procedure performs the task.
    static void ThreadProc(Object stateInfo) {
        // No state object was passed to QueueUserWorkItem, so 
        // stateInfo is null.
        Console.WriteLine("Hello from the thread pool.");
    }
}

Proporcionar datos de tareas para QueueUserWorkItem

En el siguiente ejemplo de código se utiliza el método QueueUserWorkItem para poner en cola una tarea y proporcionar los datos de esa tarea.

Imports System
Imports System.Threading
' TaskInfo holds state information for a task that will be
' executed by a ThreadPool thread.
Public Class TaskInfo
    ' State information for the task.  These members
    ' can be implemented as read-only properties, read/write
    ' properties with validation, and so on, as required.
    Public Boilerplate As String
    Public Value As Integer

    ' Public constructor provides an easy way to supply all
    ' the information needed for the task.
    Public Sub New(text As String, number As Integer)
        Boilerplate = text
        Value = number
    End Sub
End Class

Public Class Example
    Public Shared Sub Main()
        ' Create an object containing the information needed
        ' for the task.
        Dim ti As New TaskInfo("This report displays the number {0}.", 42)

        ' Queue the task and data.
        If ThreadPool.QueueUserWorkItem( _
            New WaitCallback(AddressOf ThreadProc), ti) Then
        
            Console.WriteLine("Main thread does some work, then sleeps.")

            ' If you comment out the Sleep, the main thread exits before
            ' the ThreadPool task has a chance to run.  ThreadPool uses 
            ' background threads, which do not keep the application 
            ' running.  (This is a simple example of a race condition.)
            Thread.Sleep(1000)

            Console.WriteLine("Main thread exits.")
        Else
            Console.WriteLine("Unable to queue ThreadPool request.")
        End If
    End Sub

    ' The thread procedure performs the independent task, in this case
    ' formatting and printing a very simple report.
    '
    Shared Sub ThreadProc(stateInfo As Object)
        Dim ti As TaskInfo = CType(stateInfo, TaskInfo)
        Console.WriteLine(ti.Boilerplate, ti.Value)
    End Sub
End Class
using System;
using System.Threading;

// TaskInfo holds state information for a task that will be
// executed by a ThreadPool thread.
public class TaskInfo {
    // State information for the task.  These members
    // can be implemented as read-only properties, read/write
    // properties with validation, and so on, as required.
    public string Boilerplate;
    public int Value;

    // Public constructor provides an easy way to supply all
    // the information needed for the task.
    public TaskInfo(string text, int number) {
        Boilerplate = text;
        Value = number;
    }
}

public class Example {
    public static void Main() {
        // Create an object containing the information needed
        // for the task.
        TaskInfo ti = new TaskInfo("This report displays the number {0}.", 42);

        // Queue the task and data.
        if (ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), ti)) {    
            Console.WriteLine("Main thread does some work, then sleeps.");

            // If you comment out the Sleep, the main thread exits before
            // the ThreadPool task has a chance to run.  ThreadPool uses 
            // background threads, which do not keep the application 
            // running.  (This is a simple example of a race condition.)
            Thread.Sleep(1000);

            Console.WriteLine("Main thread exits.");
        }
        else {
            Console.WriteLine("Unable to queue ThreadPool request."); 
        }
    }

    // The thread procedure performs the independent task, in this case
    // formatting and printing a very simple report.
    //
    static void ThreadProc(Object stateInfo) {
        TaskInfo ti = (TaskInfo) stateInfo;
        Console.WriteLine(ti.Boilerplate, ti.Value); 
    }
}

RegisterWaitForSingleObject

En el ejemplo siguiente se muestran diversas características del subprocesamiento.

Imports System
Imports System.Threading

' TaskInfo contains data that will be passed to the callback
' method.
Public Class TaskInfo
    public Handle As RegisteredWaitHandle = Nothing
    public OtherInfo As String = "default"
End Class

Public Class Example
    Public Shared Sub Main()
        ' The main thread uses AutoResetEvent to signal the
        ' registered wait handle, which executes the callback
        ' method.
        Dim ev As New AutoResetEvent(false)

        Dim ti As New TaskInfo()
        ti.OtherInfo = "First task"
        ' The TaskInfo for the task includes the registered wait
        ' handle returned by RegisterWaitForSingleObject.  This
        ' allows the wait to be terminated when the object has
        ' been signaled once (see WaitProc).
        ti.Handle = ThreadPool.RegisterWaitForSingleObject( _
            ev, _
            New WaitOrTimerCallback(AddressOf WaitProc), _
            ti, _
            1000, _
            false _
        )

        ' The main thread waits about three seconds, to demonstrate 
        ' the time-outs on the queued task, and then signals.
        Thread.Sleep(3100)
        Console.WriteLine("Main thread signals.")
        ev.Set()

        ' The main thread sleeps, which should give the callback
        ' method time to execute.  If you comment out this line, the
        ' program usually ends before the ThreadPool thread can execute.
        Thread.Sleep(1000)
        ' If you start a thread yourself, you can wait for it to end
        ' by calling Thread.Join.  This option is not available with 
        ' thread pool threads.
    End Sub
   
    ' The callback method executes when the registered wait times out,
    ' or when the WaitHandle (in this case AutoResetEvent) is signaled.
    ' WaitProc unregisters the WaitHandle the first time the event is 
    ' signaled.
    Public Shared Sub WaitProc(state As Object, timedOut As Boolean)
        ' The state object must be cast to the correct type, because the
        ' signature of the WaitOrTimerCallback delegate specifies type
        ' Object.
        Dim ti As TaskInfo = CType(state, TaskInfo)

        Dim cause As String = "TIMED OUT"
        If Not timedOut Then
            cause = "SIGNALED"
            ' If the callback method executes because the WaitHandle is
            ' signaled, stop future execution of the callback method
            ' by unregistering the WaitHandle.
            If Not ti.Handle Is Nothing Then
                ti.Handle.Unregister(Nothing)
            End If
        End If 

        Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.", _
            ti.OtherInfo, _
            Thread.CurrentThread.GetHashCode().ToString(), _
            cause _
        )
    End Sub
End Class
using System;
using System.Threading;

// TaskInfo contains data that will be passed to the callback
// method.
public class TaskInfo {
    public RegisteredWaitHandle Handle = null;
    public string OtherInfo = "default";
}

public class Example {
    public static void Main(string[] args) {
        // The main thread uses AutoResetEvent to signal the
        // registered wait handle, which executes the callback
        // method.
        AutoResetEvent ev = new AutoResetEvent(false);

        TaskInfo ti = new TaskInfo();
        ti.OtherInfo = "First task";
        // The TaskInfo for the task includes the registered wait
        // handle returned by RegisterWaitForSingleObject.  This
        // allows the wait to be terminated when the object has
        // been signaled once (see WaitProc).
        ti.Handle = ThreadPool.RegisterWaitForSingleObject(
            ev,
            new WaitOrTimerCallback(WaitProc),
            ti,
            1000,
            false
        );

        // The main thread waits three seconds, to demonstrate the
        // time-outs on the queued thread, and then signals.
        Thread.Sleep(3100);
        Console.WriteLine("Main thread signals.");
        ev.Set();

        // The main thread sleeps, which should give the callback
        // method time to execute.  If you comment out this line, the
        // program usually ends before the ThreadPool thread can execute.
        Thread.Sleep(1000);
        // If you start a thread yourself, you can wait for it to end
        // by calling Thread.Join.  This option is not available with 
        // thread pool threads.
    }
   
    // The callback method executes when the registered wait times out,
    // or when the WaitHandle (in this case AutoResetEvent) is signaled.
    // WaitProc unregisters the WaitHandle the first time the event is 
    // signaled.
    public static void WaitProc(object state, bool timedOut) {
        // The state object must be cast to the correct type, because the
        // signature of the WaitOrTimerCallback delegate specifies type
        // Object.
        TaskInfo ti = (TaskInfo) state;

        string cause = "TIMED OUT";
        if (!timedOut) {
            cause = "SIGNALED";
            // If the callback method executes because the WaitHandle is
            // signaled, stop future execution of the callback method
            // by unregistering the WaitHandle.
            if (ti.Handle != null)
                ti.Handle.Unregister(null);
        } 

        Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.",
            ti.OtherInfo, 
            Thread.CurrentThread.GetHashCode().ToString(), 
            cause
        );
    }
}

Vea también

Conceptos

Subprocesos y subprocesamiento

E/S de archivos asincrónica

Temporizadores

Referencia

ThreadPool

Otros recursos

Objetos y características de subprocesos