public class ProcesseurParalleleMixte
{
private readonly CancellationTokenSource _sourceGlobale = new();
public async Task TraiterFichiersMixtesAsync(IEnumerable<string> fichiers)
{
var (grosFichiers, petitsFichiers) = ClassifierFichiers(fichiers);
using var sourceLiee = CancellationTokenSource.CreateLinkedTokenSource(
_sourceGlobale.Token,
new CancellationTokenSource(TimeSpan.FromMinutes(30)).Token
);
try
{
await Task.WhenAll(
TraiterGrosFichiersAsync(grosFichiers, sourceLiee.Token),
TraiterPetitsFichiersAsync(petitsFichiers, sourceLiee.Token)
);
}
catch (OperationCanceledException)
{
Console.WriteLine("Traitement annulé");
}
}
public void Annuler() => _sourceGlobale.Cancel();
}
2. Annulation dans le traitement des gros fichiers (Parallel.ForEach)
private Task TraiterGrosFichiersAsync(List<string> fichiers, CancellationToken jeton)
{
return Task.Run(() =>
{
try
{
var options = new ParallelOptions
{
CancellationToken = jeton,
MaxDegreeOfParallelism = 4
};
Parallel.ForEach(fichiers, options, (fichier, etat) =>
{
jeton.ThrowIfCancellationRequested();
TraiterUnGrosFichier(fichier, jeton);
});
}
catch (OperationCanceledException)
{
NettoyerRessourcesGrosFichiers();
throw;
}
}, jeton);
}
private void TraiterUnGrosFichier(string fichier, CancellationToken jeton)
{
using var memMap = MemoryMappedFile.CreateFromFile(fichier);
using var accesseur = memMap.CreateViewAccessor();
long position = 0;
const long tailleBloc = 100 * 1024 * 1024;
while (position < accesseur.Capacity)
{
jeton.ThrowIfCancellationRequested();
TraiterBloc(accesseur, position, Math.Min(tailleBloc, accesseur.Capacity - position), jeton);
position += tailleBloc;
}
}
3. Annulation dans le traitement des petits fichiers (Task.WhenAll)
private async Task TraiterPetitsFichiersAsync(List<string> fichiers, CancellationToken jeton)
{
var taches = new List<Task>();
var limiteur = new SemaphoreSlim(Environment.ProcessorCount * 2);
foreach (var fichier in fichiers)
{
jeton.ThrowIfCancellationRequested();
await limiteur.WaitAsync(jeton);
taches.Add(Task.Run(async () =>
{
try
{
await TraiterUnPetitFichierAsync(fichier, jeton);
}
finally
{
limiteur.Release();
}
}, jeton));
}
try
{
await Task.WhenAll(taches);
}
catch
{
_sourceGlobale.Cancel();
throw;
}
}
private async Task TraiterUnPetitFichierAsync(string fichier, CancellationToken jeton)
{
var donnees = await File.ReadAllBytesAsync(fichier, jeton);
await Task.Run(() =>
{
for (int i = 0; i < donnees.Length; i++)
{
if (i % 1000 == 0) jeton.ThrowIfCancellationRequested();
// Traitement CPU...
}
}, jeton);
}
4. Stratégie de gestion des exceptions d’annulation
public async Task TraiterAvecSecurite(IEnumerable<string> fichiers)
{
try
{
await TraiterFichiersMixtesAsync(fichiers);
}
catch (OperationCanceledException ex)
{
if (_sourceGlobale.IsCancellationRequested)
Journaliser("Annulation utilisateur");
else if (ex.CancellationToken.IsCancellationRequested)
Journaliser("Expiration du délai ou annulation interne");
}
catch (AggregateException ae)
{
ae.Flatten().Handle(ex =>
{
if (ex is OperationCanceledException oce)
{
Journaliser("Boucle parallèle annulée");
return true;
}
return false;
});
}
finally
{
_sourceGlobale.Dispose();
}
}
5. Points techniques clés
| Scénario | Méthode d’annulation | Remarque |
|---|---|---|
| Global | CancellationTokenSource principal | Utiliser CreateLinkedTokenSource |
| Gros fichiers (Parallel) | ParallelOptions.CancellationToken + ThrowIfCancellationRequested périodique | Éviter les longues opérations sans vérification |
| Petits fichiers (Task) | Jeton passé à Task.Run et I/O asynchrone | Combiner avec SemaphoreSlim.WaitAsync |
| Traitement par blocs | Vérification avant chaque bloc | La taille du bloc affecte la réactivité |
| Nettoyage des ressources | catch/finally + using | Libérer handles et mémoire |
| Propagation des exceptions | Relancer OperationCanceledException après capture | Ne pas engloutir l’exception |
6. Bonnes pratiques
- Passage systématique du jeton : Toute sginature de méthode doit inclure
CancellationToken. - Points de contrôle optimisés : Vérifier toutes les N itérations dans les boucles CPU intensives.
- Réponse progressive : Utiliser
ThrowIfCancellationRequestedpour une annulation immédiate, ouIsCancellationRequestedpour terminer le bloc en cours. - Nettoyage via callback : Enregistrer un callback avec
token.Registerpour libérer des ressources spécifiques.
7. Exemple de cycle de vie complet
public async Task ExecuterAvecAnnulation()
{
var processeur = new ProcesseurParalleleMixte();
var fichiers = ObtenirFichiers();
using var sourceTemporisation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
using var sourceUtilisateur = new CancellationTokenSource();
using var sourceLiee = CancellationTokenSource.CreateLinkedTokenSource(
sourceTemporisation.Token, sourceUtilisateur.Token);
try
{
await processeur.TraiterFichiersMixtesAsync(fichiers, sourceLiee.Token);
}
finally
{
processeur.Dispose();
}
}
Avec cette conception, les tâches parallèles mixtes bénéficient d’un point d’annulation unique, d’une libération sécurisée des ressources, d’une réactivité rapide, et d’une isolation des exceptions.