Comment : utiliser un pool de threads (Guide de programmation C#)

Mise à jour : novembre 2007

Un pool de threads est une collection de threads qui peut être utilisée pour effectuer plusieurs tâches en arrière-plan. (Pour des informations générales, consultez Utilisation de threads (Guide de programmation C#).) Cela laisse le thread principal libre pour effectuer d'autres tâches de façon asynchrone.

Les pools de threads sont souvent employés dans des applications serveur. Chaque requête entrante est assignée à un thread du pool de threads, de sorte que la demande peut être traitée de façon asynchrone, sans lier le thread principal ou différer le traitement de demandes suivantes.

Une fois qu'un thread du pool complète sa tâche, il est retourné à une file d'attente de threads en attente, où il peut être réutilisé. Cette réutilisation permet aux applications d'épargner le coût de la création d'un thread pour chaque tâche.

Les pools de threads ont en général un nombre maximal de threads. Si tous les threads sont occupés, des tâches supplémentaires sont placées dans la file d'attente jusqu'à ce qu'ils puissent être gérés à mesure que les threads deviennent disponibles.

Vous pouvez implémenter votre propre pool de threads, mais il est plus facile d'utiliser le pool de threads fourni par .NET Framework par le biais de la classe ThreadPool.

L'exemple suivant utilise le pool de threads .NET Framework pour calculer le résultat Fibonacci pour dix nombres entre 20 et 40. Chaque résultat Fibonacci est représenté par la classe Fibonacci, qui fournit une méthode nommée ThreadPoolCallback qui effectue le calcul. Un objet qui représente chaque valeur de Fibonacci est créé, et la méthode ThreadPoolCallback est passée à QueueUserWorkItem, qui assigne un thread disponible du pool pour exécuter la méthode.

Comme chaque objet Fibonacci se voit attribuer une valeur semi-aléatoire à calculer et que chaque thread sera en concurrence pour le temps processeur, vous ne pouvez pas savoir à l'avance le temps qu'il faudra pour calculer les dix résultats. C'est pourquoi une instance de la classe ManualResetEvent est passée à chaque objet Fibonacci pendant la construction. Chaque objet signale à l'objet événement fourni que son calcul est terminé, ce qui permet au thread principal de bloquer l'exécution avec WaitAll jusqu'à ce que les dix objets Fibonacci aient calculé un résultat. La méthode Main affiche ensuite chaque résultat Fibonacci.

Exemple

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

Voici la sortie :

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

Voir aussi

Tâches

Synchronisation de moniteurs, exemple de technologie

Synchronisation d'attente, exemple de technologie

Concepts

Guide de programmation C#

Moniteurs

Référence

Thread (Guide de programmation C#)

Utilisation de threads (Guide de programmation C#)

Mutex

WaitAll

ManualResetEvent

Set

ThreadPool

QueueUserWorkItem

ManualResetEvent

Autres ressources

Comment : synchroniser l'accès à une ressource partagée dans un environnement de multithreading à l'aide de Visual C# .NET

Sécurité dans le .NET Framework