Gestion de l’annulation dans les tâches parallèles mixtes avec Task.Run, Task.WhenAll et Parallel


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 ThrowIfCancellationRequested pour une annulation immédiate, ou IsCancellationRequested pour terminer le bloc en cours.
  • Nettoyage via callback : Enregistrer un callback avec token.Register pour 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.

Étiquettes: Task.Run CancellationToken Parallel.ForEach Task.WhenAll async/await

Publié le 7 juillet à 00h15