Condividi tramite


Procedura: utilizzare un pool di thread (Guida per programmatori C#)

Aggiornamento: novembre 2007

Un pool di thread è un insieme di thread che è possibile utilizzare per eseguire diverse attività in background. Per informazioni complementari, vedere Utilizzo del threading (Guida per programmatori C#). In questo modo il thread primario è libero di eseguire altre attività in modo asincrono.

I pool di thread vengono spesso utilizzati nelle applicazioni server. Ad ogni richiesta in arrivo viene assegnato un thread del pool. In questo modo la richiesta può essere elaborata in modo asincrono, senza bloccare il thread primario o ritardare l'elaborazione delle richieste successive.

Una volta completata l'attività, il thread del pool torna in una coda di thread in attesa, dove potrà essere riutilizzato evitando in questo modo la necessità di creare un nuovo thread per ogni attività.

Esiste in genere un numero massimo di thread che è possibile inserire in un pool. Se tutti i thread sono occupati, le altre attività vengono inserite in coda finché non potranno essere eseguite dai thread che si renderanno disponibili.

È possibile implementare un pool di thread personalizzato, ma risulta più agevole utilizzare quello fornito con .NET Framework tramite la classe ThreadPool.

Nell'esempio seguente viene utilizzato un pool di thread di .NET Framework per calcolare il risultato Fibonacci relativo a dieci numeri compresi tra 20 e 40. Ogni risultato Fibonacci è rappresentato dalla classe Fibonacci, che fornisce un metodo denominato ThreadPoolCallback per l'esecuzione del calcolo. Viene creato un oggetto che rappresenta ogni valore Fibonacci, quindi il metodo ThreadPoolCallback viene passato a QueueUserWorkItem, che assegna un thread disponibile del pool all'esecuzione del metodo.

Poiché a ogni oggetto Fibonacci viene assegnato un valore semi-casuale da calcolare e poiché ogni thread sarà in competizione per ottenere il tempo del processore, non è possibile prevedere quanto tempo richiederà il calcolo di tutti e dieci i risultati. Per questo motivo a ogni oggetto Fibonacci viene passata un'istanza della classe ManualResetEvent durante la costruzione. Una volta completato il calcolo, ogni oggetto segnala l'oggetto evento fornito. In questo modo il thread primario può bloccare l'esecuzione con WaitAll finché tutti e dieci gli oggetti Fibonacci non hanno calcolato un risultato. Il metodo Main visualizza quindi ogni risultato Fibonacci.

Esempio

using System;
using System.Threading;

public class Fibonacci
{
    public Fibonacci(int n, ManualResetEvent doneEvent)
    {
        _n = n;
        _doneEvent = doneEvent;
    }

    // Wrapper method for use with thread pool.
    public void ThreadPoolCallback(Object threadContext)
    {
        int threadIndex = (int)threadContext;
        Console.WriteLine("thread {0} started...", threadIndex);
        _fibOfN = Calculate(_n);
        Console.WriteLine("thread {0} result calculated...", threadIndex);
        _doneEvent.Set();
    }

    // Recursive method that calculates the Nth Fibonacci number.
    public int Calculate(int n)
    {
        if (n <= 1)
        {
            return n;
        }

        return Calculate(n - 1) + Calculate(n - 2);
    }

    public int N { get { return _n; } }
    private int _n;

    public int FibOfN { get { return _fibOfN; } }
    private int _fibOfN;

    private ManualResetEvent _doneEvent;
}

public class ThreadPoolExample
{
    static void Main()
    {
        const int FibonacciCalculations = 10;

        // One event is used for each Fibonacci object
        ManualResetEvent[] doneEvents = new ManualResetEvent[FibonacciCalculations];
        Fibonacci[] fibArray = new Fibonacci[FibonacciCalculations];
        Random r = new Random();

        // Configure and launch threads using ThreadPool:
        Console.WriteLine("launching {0} tasks...", FibonacciCalculations);
        for (int i = 0; i < FibonacciCalculations; i++)
        {
            doneEvents[i] = new ManualResetEvent(false);
            Fibonacci f = new Fibonacci(r.Next(20,40), doneEvents[i]);
            fibArray[i] = f;
            ThreadPool.QueueUserWorkItem(f.ThreadPoolCallback, i);
        }

        // Wait for all threads in pool to calculation...
        WaitHandle.WaitAll(doneEvents);
        Console.WriteLine("All calculations are complete.");

        // Display the results...
        for (int i= 0; i<FibonacciCalculations; i++)
        {
            Fibonacci f = fibArray[i];
            Console.WriteLine("Fibonacci({0}) = {1}", f.N, f.FibOfN);
        }
    }
}

L'output è il seguente:

launching 10 tasks...
result calculated...
result calculated...
result calculated...
result calculated...
result calculated...
result calculated...
result calculated...
result calculated...
result calculated...
result calculated...
all calculations complete
Fibonacci(22) = 17711
Fibonacci(25) = 75025
Fibonacci(32) = 2178309
Fibonacci(36) = 14930352
Fibonacci(32) = 2178309
Fibonacci(26) = 121393
Fibonacci(35) = 9227465
Fibonacci(23) = 28657
Fibonacci(39) = 63245986
Fibonacci(22) = 17711

Vedere anche

Attività

Esempio di tecnologia della sincronizzazione monitor

Esempio di tecnologia della sincronizzazione di attesa

Concetti

Guida per programmatori C#

Monitor

Riferimenti

Threading (Guida per programmatori C#)

Utilizzo del threading (Guida per programmatori C#)

Mutex

WaitAll

ManualResetEvent

Set

ThreadPool

QueueUserWorkItem

ManualResetEvent

Altre risorse

HOW TO: Synchronize Access to a Shared Resource in a Multithreading Environment by Using Visual C# .NET

Protezione in .NET Framework