🔹 1. Quelle est la différence entre un Thread et un Task en C# ?
✅ Thread :
-
Représente une unité d’exécution du système d’exploitation.
-
Créé et géré manuellement (via la classe
Thread). -
Plus bas-niveau : on contrôle directement le cycle de vie du thread.
-
Utilise plus de ressources (chaque thread a sa propre pile mémoire).
-
Exemple :
Thread t = new Thread(() => Console.WriteLine("Thread running")); t.Start(); t.Join(); // attendre la fin du thread
✅ Task :
-
Représente une unité de travail logique, non nécessairement liée à un thread unique.
-
Gérée par le ThreadPool.
-
Plus haut niveau : gère la synchronisation, les exceptions et le retour de valeur (
Task<T>). -
Plus adaptée à la programmation asynchrone et parallèle moderne.
-
Exemple :
Task task = Task.Run(() => Console.WriteLine("Task running")); await task;
🧠 En résumé :
Thread= contrôle manuel, plus coûteux.
Task= abstraction haut niveau optimisée, intégrée au modèle async/await.
🔹 2. Comment créer et exécuter un thread en C# ?
Exemple :
using System;
using System.Threading;
class Program
{
static void Main()
{
Thread t = new Thread(new ThreadStart(DoWork));
t.Start(); // Démarre le thread
t.Join(); // Attend la fin
}
static void DoWork()
{
Console.WriteLine($"Running on thread {Thread.CurrentThread.ManagedThreadId}");
}
}
➡️ On peut aussi passer des paramètres avec ParameterizedThreadStart ou une lambda.
🔹 3. Expliquez l’utilisation de lock, Monitor et Mutex en C#.
Ces trois mécanismes servent à synchroniser l’accès à des ressources partagées entre plusieurs threads.
| Mécanisme | Portée | Utilisation typique | Exemple |
|---|---|---|---|
| lock | Intra-processus | Section critique simple | lock(obj) { ... } |
| Monitor | Intra-processus | Équivalent à lock, mais plus flexible (Wait, Pulse) |
Monitor.Enter(obj); try {...} finally { Monitor.Exit(obj); } |
| Mutex | Inter-processus | Synchronisation entre plusieurs processus | new Mutex(false, "Global\\MyMutex") |
Exemple lock :
private static object _sync = new object();
lock (_sync)
{
// Code thread-safe
}
Exemple Mutex :
using (var mutex = new Mutex(false, "Global\\MyApp"))
{
if (!mutex.WaitOne(1000))
Console.WriteLine("Instance déjà en cours");
else
Console.WriteLine("Instance unique");
}
🔹 4. Quelle est la différence entre Parallel.For et Task.Run ?
| Critère | Parallel.For |
Task.Run |
|---|---|---|
| Objectif | Exécuter plusieurs itérations en parallèle | Lancer une tâche asynchrone |
| Niveau | Très haut niveau (boucles parallèles) | Moyen niveau (unités de travail) |
| Gestion | Automatique des threads via le ThreadPool | Contrôle explicite de chaque tâche |
| Retour | Bloquant (synchrone) | Asynchrone (attendable avec await) |
Exemple :
// Parallel.For : boucle parallèle
Parallel.For(0, 5, i =>
{
Console.WriteLine($"Iteration {i} - Thread {Thread.CurrentThread.ManagedThreadId}");
});
// Task.Run : tâche asynchrone
await Task.Run(() => DoWork());
🔹 5. Qu’est-ce qu’un ThreadPool et quand l’utiliser ?
Le ThreadPool est un pool de threads réutilisables géré par le CLR.
Les Task, Parallel, async/await utilisent le ThreadPool.
Avantages :
-
Réduit le coût de création/destruction de threads.
-
Optimisé pour un grand nombre de petites tâches courtes.
Exemple :
ThreadPool.QueueUserWorkItem(_ => Console.WriteLine("ThreadPool work item"));
🧠 À utiliser pour : tâches courtes, nombreuses, indépendantes.
À éviter pour : tâches longues, bloquantes (préférer un Thread dédié).
🔹 6. Comment utiliser async et await pour la programmation asynchrone en C# ?
-
async: marque une méthode comme asynchrone. -
await: suspend l’exécution jusqu’à la fin d’une tâche, sans bloquer le thread courant.
Exemple :
public async Task DownloadFileAsync()
{
using (HttpClient client = new HttpClient())
{
string content = await client.GetStringAsync("https://example.com");
Console.WriteLine(content);
}
}
➡️ await libère le thread pendant l’attente et reprend l’exécution quand la tâche est terminée.
🔹 7. Qu’est-ce que la classe CancellationToken et comment l’utiliser ?
Permet d’annuler proprement des tâches asynchrones ou des opérations parallèles.
Exemple :
var cts = new CancellationTokenSource();
var token = cts.Token;
var task = Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested();
Console.WriteLine(i);
Thread.Sleep(500);
}
}, token);
Thread.Sleep(2000);
cts.Cancel();
try { await task; }
catch (OperationCanceledException)
{
Console.WriteLine("Tâche annulée");
}
🔹 8. Quelle est la différence entre le modèle synchrone et asynchrone ?
| Type | Comportement | Avantage | Inconvénient |
|---|---|---|---|
| Synchrone | Le thread attend la fin de chaque opération | Simple à comprendre | Bloquant, peu scalable |
| Asynchrone | Le thread continue pendant qu’une opération se termine | Réactif, scalable | Complexité accrue |
🧠 Exemple :
// Synchrone
var data = client.GetString("https://api.com");
// Asynchrone
var data = await client.GetStringAsync("https://api.com");
🔹 9. Comment éviter les deadlocks dans une application multi-thread ?
Bonnes pratiques :
-
Toujours acquérir les verrous dans le même ordre.
-
Minimiser la durée du verrou (
lockle moins longtemps possible). -
Éviter les appels bloquants dans une section critique.
-
Utiliser
awaitplutôt que.Resultou.Wait()sur uneTask. -
Préférer
ConfigureAwait(false)dans les bibliothèques pour éviter les captures de contexte UI.
Exemple de piège :
// ❌ Deadlock potentiel
var result = Task.Run(() => GetDataAsync()).Result;
🔹 10. Expliquez Semaphore et SemaphoreSlim avec des exemples.
✅ Semaphore :
-
Permet de limiter le nombre de threads simultanés accédant à une ressource.
-
Fonctionne entre processus différents.
Semaphore semaphore = new Semaphore(2, 2); // 2 threads max
for (int i = 0; i < 5; i++)
{
new Thread(() =>
{
semaphore.WaitOne();
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} entre");
Thread.Sleep(1000);
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} sort");
semaphore.Release();
}).Start();
}
✅ SemaphoreSlim :
-
Version plus légère, intra-processus, utilisée dans les scénarios async.
SemaphoreSlim semaphore = new SemaphoreSlim(2, 2);
var tasks = Enumerable.Range(0, 5).Select(async i =>
{
await semaphore.WaitAsync();
Console.WriteLine($"Task {i} running");
await Task.Delay(1000);
semaphore.Release();
});
await Task.WhenAll(tasks);
🔹 1. Qu'est-ce que le SynchronizationContext en C# et comment fonctionne-t-il ?
✅ Définition :
SynchronizationContext représente un contexte d’exécution logique qui définit où et comment le code asynchrone doit reprendre après un await.
-
Dans une application UI (WinForms, WPF), le
SynchronizationContextpermet de reprendre l’exécution sur le thread principal UI après unawait. -
Dans une application console ou ASP.NET Core, le contexte est souvent nul ou neutre, donc le code reprend sur n’importe quel thread du pool.
🧩 Exemple :
private async void Button_Click(object sender, EventArgs e)
{
// Capturer le SynchronizationContext (thread UI)
await Task.Delay(1000);
// Reprise sur le thread UI (grâce au SynchronizationContext)
label1.Text = "Done!";
}
⚙️ Fonctionnement :
-
Lors du
await, le runtime capture leSynchronizationContextactuel. -
Quand la tâche reprend, elle poste le reste du code dans ce contexte.
-
Cela permet d’éviter les accès concurrents à l’UI.
🧠 Résumé :
Le
SynchronizationContextgarantit que le code asynchrone reprend sur le bon thread (souvent le thread UI).
🔹 2. Différence entre ConcurrentBag, ConcurrentQueue et ConcurrentStack
| Collection | Type | Ordre d’accès | Utilisation typique |
|---|---|---|---|
| ConcurrentQueue | File (FIFO) | Premier entré, premier sorti | Files d’attente de tâches |
| ConcurrentStack | Pile (LIFO) | Dernier entré, premier sorti | Undo/Redo, historiques |
| ConcurrentBag | Sac (non ordonné) | Ordre indéterminé | Partage d’objets entre threads |
Toutes ces collections :
-
Font partie de
System.Collections.Concurrent -
Sont thread-safe
-
Utilisent des verrous fins ou lock-free structures
🧩 Exemple :
ConcurrentQueue<int> queue = new();
queue.Enqueue(1);
queue.Enqueue(2);
queue.TryDequeue(out int result); // 1 (FIFO)
🧠 Résumé :
Choisir la collection selon la politique d’accès (FIFO, LIFO, ou libre).
🔹 3. Qu’est-ce qu’un race condition et comment l’éviter en C# ?
✅ Définition :
Une race condition se produit lorsque plusieurs threads accèdent à une même ressource partagée, sans coordination, provoquant un comportement imprévisible.
🧩 Exemple problématique :
int counter = 0;
Parallel.For(0, 1000, i => counter++); // ❌ accès concurrent
Console.WriteLine(counter); // Résultat incorrect
✅ Solutions :
-
Utiliser un
lock:object locker = new object(); Parallel.For(0, 1000, i => { lock (locker) counter++; }); -
Utiliser
Interlocked:Parallel.For(0, 1000, i => Interlocked.Increment(ref counter));
🧠 Résumé :
Les race conditions sont évitées via des mécanismes de synchronisation (
lock,Monitor,Interlocked, etc.).
🔹 4. Comment utiliser Task.WhenAll() et Task.WhenAny() ?
✅ Task.WhenAll() :
Exécute plusieurs tâches en parallèle et attend qu’elles soient toutes terminées.
var t1 = Task.Delay(1000);
var t2 = Task.Delay(2000);
await Task.WhenAll(t1, t2); // Attend les deux
Console.WriteLine("All tasks done");
✅ Task.WhenAny() :
Attend que la première tâche terminée renvoie un résultat.
var t1 = Task.Delay(1000);
var t2 = Task.Delay(3000);
await Task.WhenAny(t1, t2);
Console.WriteLine("First task done");
🧠 Résumé :
WhenAll→ synchronisation globale,
WhenAny→ synchronisation partielle (utile pour les timeouts ou priorités).
🔹 5. Différence entre ConfigureAwait(true) et ConfigureAwait(false)
| Option | Description | Comportement |
|---|---|---|
| true (par défaut) | Capture le SynchronizationContext courant |
Reprise sur le thread d’origine (ex : UI) |
| false | Ignore le contexte capturé | Reprise sur n’importe quel thread du pool |
🧩 Exemple :
await Task.Delay(1000).ConfigureAwait(false);
👉 Ici, la suite du code s’exécute sur un autre thread, utile pour optimiser les performances côté serveur (ASP.NET Core).
🧠 Bonnes pratiques :
-
UI apps →
ConfigureAwait(true) -
Backend/server →
ConfigureAwait(false)
🔹 6. Comment utiliser BlockingCollection pour synchroniser des threads ?
✅ Définition :
BlockingCollection<T> est une collection thread-safe qui bloque les producteurs quand elle est pleine et bloque les consommateurs quand elle est vide.
✅ Cas d’usage :
Idéale pour les modèles producteur-consommateur.
🧩 Exemple :
BlockingCollection<int> collection = new(5);
// Producteur
Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
collection.Add(i);
Console.WriteLine($"Produced {i}");
}
collection.CompleteAdding();
});
// Consommateur
foreach (var item in collection.GetConsumingEnumerable())
{
Console.WriteLine($"Consumed {item}");
}
🧠 Résumé :
BlockingCollectionsynchronise automatiquement la production et la consommation entre plusieurs threads sans code de verrou explicite.
🔹 7. Qu’est-ce qu’un SpinLock et quand faut-il l’utiliser ?
✅ Définition :
SpinLock est un verrou bas niveau qui occupe activement le CPU en boucle tant que le verrou n’est pas libéré.
✅ Fonctionnement :
Contrairement à lock, il ne met pas le thread en sommeil : il reste actif (busy-wait).
🧩 Exemple :
SpinLock spinLock = new SpinLock();
bool taken = false;
try
{
spinLock.Enter(ref taken);
// Section critique
}
finally
{
if (taken) spinLock.Exit();
}
⚠️ Utilisation :
-
À éviter sauf pour sections critiques très courtes (< 1 µs).
-
Utilisé dans des systèmes temps réel ou très bas niveau.
🧠 Résumé :
SpinLockévite le coût de planification des threads, mais consomme du CPU.
À réserver pour les verrous très courts.
🔹 8. Qu’est-ce que le thread affinity et pourquoi est-il important dans les applications UI ?
✅ Définition :
Le thread affinity signifie qu’un objet ou une ressource doit être accédé uniquement par un thread spécifique.
Exemple : dans WPF ou WinForms, les éléments UI ne peuvent être modifiés que depuis le thread principal UI.
🧩 Exemple :
// ❌ Provoque une exception
Task.Run(() => label1.Text = "Test");
// ✅ Correction
Task.Run(() => this.Invoke(() => label1.Text = "Test"));
🧠 Pourquoi important :
Cela évite les cross-thread exceptions et garantit la cohérence de l’état graphique.
🔹 9. Différence entre volatile, lock et Interlocked
| Mot-clé / Classe | Type | Rôle principal | Garanties |
|---|---|---|---|
volatile |
Modificateur de champ | Empêche la mise en cache de la variable entre threads | Visibilité ✅ / Atomicité ❌ |
lock |
Instruction C# | Exclusivité d’accès (mutex interne) | Atomicité ✅ / Visibilité ✅ |
Interlocked |
Classe statique | Opérations atomiques sur types primitifs | Atomicité ✅ / Visibilité ✅ |
🧩 Exemple :
volatile bool flag = false;
lock(obj) { counter++; }
Interlocked.Increment(ref counter);
🧠 Résumé :
volatile→ visibilité mémoire
lock→ exclusivité complète
Interlocked→ opérations atomiques légères
🔹 10. Pourquoi await Task.Delay(1000) est-il préférable à Thread.Sleep(1000) dans une application asynchrone ?
| Aspect | Thread.Sleep() |
Task.Delay() |
|---|---|---|
| Type | Bloquant | Non bloquant |
| Thread | Bloqué pendant la durée | Libéré pour d’autres tâches |
| Utilisation | Code synchrone | Code asynchrone (async/await) |
| Performance | Inefficace (bloque un thread du pool) | Optimisée (utilise un timer interne) |
🧩 Exemple :
await Task.Delay(1000); // Laisse le thread libre ✅
Thread.Sleep(1000); // Bloque le thread ❌
🧠 Conclusion :
Task.Delay()est non bloquant → idéal dans les apps asynchrones (UI, serveur).
Thread.Sleep()bloque le thread → à éviter dans tout codeasync.
📘 RÉCAPITULATIF SYNTHÉTIQUE
| Concept | Description | Niveau |
|---|---|---|
SynchronizationContext |
Gère le contexte de reprise des tâches asynchrones | Intermédiaire |
Concurrent* Collections |
Structures thread-safe (Queue, Stack, Bag) | Avancé |
Race Condition |
Accès non coordonné à une ressource | Fondamental |
Task.WhenAll/WhenAny |
Coordination de plusieurs tâches asynchrones | Moyen |
ConfigureAwait(false) |
Ignore le contexte capturé | Avancé |
BlockingCollection |
Synchronisation producteur/consommateur | Avancé |
SpinLock |
Verrou actif performant (rare) | Expert |
Thread Affinity |
Accès UI sur thread principal uniquement | Intermédiaire |
volatile / lock / Interlocked |
Synchronisation mémoire et atomicité | Fondamental |
Task.Delay vs Thread.Sleep |
Différence entre blocage et attente asynchrone | Fondamental |
Aucun commentaire:
Enregistrer un commentaire