Condividi tramite


Pool di thread gestiti

Aggiornamento: novembre 2007

La classe ThreadPool fornisce all'applicazione un pool di thread di lavoro gestiti dal sistema, che consentono di concentrarsi sulle attività dell'applicazione anziché sulla gestione dei thread. Se si dispone di attività brevi per cui è necessaria l'elaborazione in background, il pool di thread gestiti rappresenta la tecnica più semplice per utilizzare più thread.

Nota:

A partire da .NET Framework 2.0 Service Pack 1, la trasmissione dei dati del pool di thread è migliorata significativamente in tre aree principali considerate come colli di bottiglia nelle versioni precedenti di .NET Framework: mettendo in coda attività, inviando thread del pool di thread e inviando thread di completamento di I/O. Per utilizzare questa funzionalità, l'applicazione deve essere indirizzata a .NET Framework versione 3.5. Per ulteriori informazioni, vedere Architettura di .NET Framework 3.5.

Per le attività in background che interagiscono con l'interfaccia utente, .NET Framework versione 2.0 fornisce anche la classe BackgroundWorker, che comunica utilizzando gli eventi che si sono verificati nel thread dell'interfaccia utente.

.NET Framework utilizza i thread del pool di thread per diversi scopi, ad esempio il completamento I/O asincrono, i callback del timer, le operazioni di attesa registrate, le chiamate ai metodi asincroni effettuate tramite delegati e le connessioni socket a System.Net.

Situazioni in cui non è opportuno utilizzare i thread del pool di thread

Esistono diversi scenari in cui è preferibile creare e gestire thread personalizzati anziché utilizzare quelli del pool, in particolare quando:

  • È necessario un thread in primo piano.

  • È necessario che un thread abbia una determinata priorità.

  • Sono presenti attività che bloccano il thread per periodi di tempo prolungati. È previsto un numero massimo di thread del pool e, pertanto, è possibile che il blocco di numerosi thread del pool impedisca l'avvio delle attività.

  • È necessario inserire i thread in un apartment a thread singolo. Tutti i thread di ThreadPool sono inclusi in apartment con multithreading.

  • È necessario disporre di un'identità stabile associata al thread oppure utilizzare un thread esclusivamente per un'attività.

Caratteristiche del pool di thread

I thread del pool sono thread in background. Per informazioni, vedere Thread in primo piano e in background. Ogni thread utilizza una dimensione di stack predefinita, viene eseguito alla priorità predefinita e si trova nell'apartment con multithreading.

È disponibile un solo pool di thread per processo.

Eccezioni nei thread del pool di thread

Le eccezioni non gestite nei thread del pool determinano l'interruzione del processo. Esistono tre eccezioni a questa regola:

  • Viene generata un'eccezione ThreadAbortException in un thread del pool per effetto di una chiamata a Abort.

  • Viene generata un'eccezione AppDomainUnloadedException in un thread del pool perché è in corso lo scaricamento del dominio applicazione.

  • Il thread viene interrotto da Common Language Runtime o da un processo host.

Per ulteriori informazioni, vedere Eccezioni in thread gestiti.

Nota:

In .NET Framework versioni 1.0 e 1.1 Common Language Runtime intercetta automaticamente le eccezioni non gestite nei thread del pool. Questo comportamento può danneggiare lo stato dell'applicazione ed eventualmente determinare il blocco delle applicazioni, rendendo particolarmente difficile il debug.

Numero massimo di thread del pool di thread

Il numero di operazioni che possono essere accodate al pool di thread è limitato solo dalla disponibilità di memoria. Tuttavia, il pool di thread limita il numero di thread che possono essere attivi simultaneamente nel processo. Per impostazione predefinita, il limite è di 25 thread di lavoro per CPU e di 1.000 thread di completamento I/O.

È possibile controllare il numero massimo di thread utilizzando i metodi GetMaxThreads e SetMaxThreads.

Nota:

In .NET Framework versioni 1.0 e 1.1 non è possibile impostare la dimensione del pool di thread dal codice gestito. Il codice in cui è incluso Common Language Runtime può impostare la dimensione utilizzando CorSetMaxThreads, definito in mscoree.h.

Numero minimo di thread inattivi

Il pool di thread mantiene inoltre un numero minimo di thread disponibili, anche quando tutti i thread sono inattivi, affinché le attività in coda possano essere avviate immediatamente. I thread inattivi che superano questo numero minimo vengono interrotti per risparmiare le risorse di sistema. Per impostazione predefinita, viene mantenuto un thread inattivo per processore.

Il pool di thread rimane in attesa per un intervallo di tempo predefinito, pari a mezzo secondo in .NET Framework versione 2.0, prima di avviare nuovi thread inattivi. Se l'applicazione avvia periodicamente numerose attività in un periodo di tempo limitato, è possibile che da un piccolo aumento del numero di thread inattivi derivi un aumento significativo nella velocità di elaborazione. Impostando un numero di thread inattivi troppo alto, le risorse di sistema verranno utilizzate inutilmente.

È possibile controllare il numero di thread inattivi mantenuti dal pool di thread utilizzando i metodi GetMinThreads e SetMinThreads.

Nota:

In .NET Framework versione 1.0 non è possibile impostare il numero minimo di thread inattivi.

Ignorare i controlli di protezione

Il pool di thread fornisce anche i metodi ThreadPool.UnsafeQueueUserWorkItem e ThreadPool.UnsafeRegisterWaitForSingleObject. Utilizzare questi metodi solo se si è certi che lo stack del chiamante non è significativo per alcuno dei controlli di protezione effettuati durante l'esecuzione dell'attività in coda. QueueUserWorkItem e RegisterWaitForSingleObject acquisiscono entrambi lo stack del chiamante, che viene unito nello stack del thread del pool in cui il thread avvia l'esecuzione di un'attività. Se è richiesto un controllo di protezione, esso deve essere eseguito in tutto lo stack. Sebbene tale controllo offra garanzie di sicurezza, ha anche dei costi in termini di prestazioni.

Utilizzo del pool di thread

Il pool di thread viene utilizzato chiamando ThreadPool.QueueUserWorkItem dal codice gestito o CorQueueUserWorkItem dal codice non gestito e passando un delegato WaitCallback che rappresenta il metodo da cui viene eseguita l'attività. È inoltre possibile accodare elementi di lavoro correlati a un'operazione di attesa utilizzando il metodo ThreadPool.RegisterWaitForSingleObject e passando un oggetto WaitHandle che, alla ricezione di un segnale o alla scadenza, genera una chiamata al metodo rappresentato dal delegato WaitOrTimerCallback. In entrambi i casi, il pool di thread utilizza un thread in background per richiamare il metodo di callback.

Esempi di ThreadPool

Nei tre esempi di codice riportati di seguito vengono illustrati i metodi QueueUserWorkItem e RegisterWaitForSingleObject.

Nel primo esempio viene accodata un'attività molto semplice, rappresentata dal metodo ThreadProc, tramite 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.");
    }
}

Fornire dati attività per QueueUserWorkItem

Nell'esempio di codice riportato di seguito viene utilizzato il metodo QueueUserWorkItem per accodare un'attività e fornire i relativi dati.

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

Nell'esempio che segue vengono illustrate diverse funzionalità del threading.

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

Vedere anche

Concetti

Thread e threading

I/O di file asincrono

Timer

Riferimenti

ThreadPool

Altre risorse

Oggetti e funzionalità del threading