Concurrence et multithreading en C# : Guide complet
Les applications modernes nécessitent des performances élevées et une réactivité optimale. La concurrence (concurrency) et le multithreading (multithreading) en C# sont des outils clés permettant d'exécuter plusieurs tâches simultanément, d'utiliser efficacement les processeurs multicœurs et d'améliorer l'expérience utilisateur. Dans cet article, nous allons analyser les concepts fondamentaux, depuis les threads classiques jusqu'à l'async/await moderne et Parallel LINQ.
Fondamentaux du multithreading : la classe Thread et le pool de threads
Le multithreading en C# commence avec la classe System.Threading.Thread. Chaque thread représente un chemin d'exécution distinct au sein d'un processus. Cependant, la création manuelle de threads est une opération coûteuse. Pour optimiser cela, on utilise le pool de threads (ThreadPool), qui gère la réutilisation des threads.
Exemple de création manuelle d'un thread :
using System;
using System.Threading;
class Program
{
static void Main()
{
Thread thread = new Thread(DoWork);
thread.Start();
Console.WriteLine("Le thread principal travaille...");
thread.Join(); // Attend la fin
}
static void DoWork()
{
Console.WriteLine("Le thread exécute le travail.");
}
}
Pour une utilisation plus simple, ThreadPool est recommandé :
ThreadPool.QueueUserWorkItem(state => {
Console.WriteLine("Tâche dans le pool de threads.");
});
Important : La classe Thread possède une API de bas niveau et ne prend pas en charge le retour de résultat. Pour les applications modernes, utilisez Task.
Task et TPL (Task Parallel Library)
La bibliothèque TPL (System.Threading.Tasks) fournit une abstraction de haut niveau — Task. Task représente une opération asynchrone qui peut retourner une valeur. C'est la base de la concurrence moderne en C#.
Exemple de Task sans valeur de retour :
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Task task = Task.Run(() => DoWork());
await task;
Console.WriteLine("Terminé.");
}
static void DoWork()
{
Console.WriteLine("Travail dans Task.");
}
}
Task avec valeur de retour :
Task<int> task = Task.Run(() => {
int result = 0;
for (int i = 0; i < 1000000; i++) result += i;
return result;
});
int sum = await task;
Console.WriteLine($"Somme : {sum}");
TPL inclut également Parallel.For et Parallel.ForEach pour le traitement parallèle des collections :
Parallel.For(0, 10, i => {
Console.WriteLine($"Itération {i} sur le thread {Thread.CurrentThread.ManagedThreadId}");
});
Async/await : programmation asynchrone
Les mots-clés async et await simplifient l'écriture de code asynchrone, le rendant similaire au code synchrone. Ils ne créent pas de nouveaux threads, mais basculent efficacement le contexte d'exécution, libérant le thread pour d'autres tâches.
Exemple de méthode asynchrone :
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
string data = await FetchDataAsync("https://example.com");
Console.WriteLine(data);
}
static async Task<string> FetchDataAsync(string url)
{
using HttpClient client = new HttpClient();
string result = await client.GetStringAsync(url);
return result;
}
}
Règles d'utilisation :
- Une méthode avec
asyncdoit retournerTask,Task<T>ouvoid(uniquement pour les gestionnaires d'événements). - Utilisez
awaituniquement à l'intérieur d'une méthode marquéeasync. - Évitez
async voidsauf pour les gestionnaires d'événements.