Langage C# – Framework .NET : Exécution de traitements de manière parallèle

Par défaut, un bloc de code C# est exécuté de manière linéaire, instruction après instruction, itération après itération, par une seule unité de traitement (un seul thread). Afin d’améliorer les performances d’exécution de ce bloc de code, Microsoft propose de l’exécuter de manière parallèle. Voici un bloc de code permettant de copier une liste de fichiers de manière linéaire :

try
{
    // Variables locales.
    DirectoryInfo oRepertoireSource, oRepertoireDestination;
    List<FileInfo> oListeFichiers;
    Stopwatch oStopWatch;
    int iNombreFichiersCopies;
    int iNombreThreads, iNombreThreadsMax;
    string sResult;

    // Initialisation.
    oRepertoireSource = new DirectoryInfo(TxtRepertoireSource.Text);
    oRepertoireDestination = new DirectoryInfo(TxtRepertoireDestination.Text);
    oListeFichiers = oRepertoireSource.GetFiles().ToList();
    oStopWatch = new Stopwatch();
    iNombreFichiersCopies = 0;
    iNombreThreadsMax = 0;
    iNombreThreads = 0;

    // Suppression de fichiers dans le répertoire de destination.
    foreach (FileInfo oFichier in oRepertoireDestination.GetFiles())
    {
        oFichier.Delete();
    }

    oStopWatch.Start();

    // Copie des fichiers.
    foreach (FileInfo oFichier in oListeFichiers)
    {
        // Copie du fichier.
        oFichier.CopyTo(TxtRepertoireDestination.Text + @"" + oFichier.Name);

        // Progression.
        BgwCopieFichiersMonoThread.ReportProgress(iNombreFichiersCopies * 100 / oListeFichiers.Count);
        iNombreFichiersCopies++;

        // Pour ne garder que ne plus grand nombre de threads dans le process.
        if (Process.GetCurrentProcess().Threads.Count > iNombreThreads)
        {
            iNombreThreadsMax = Process.GetCurrentProcess().Threads.Count;
        }
    }

    sResult = iNombreFichiersCopies.ToString() + "|" + (oStopWatch.Elapsed.TotalSeconds).ToString("0.00") + "|" + iNombreThreadsMax.ToString();
    Debug.WriteLine(sResult);
}
catch (Exception aEx)
{
    MessageBox.Show(aEx.Message);
}

Sur mon PC, pour réaliser une copie de 5000 fichiers de 7Ko, le programme indique qu’il les a copiés en 29,58 secondes avec un seul thread.

Pour optimiser l’exécution des traitements en utilisant au mieux les processeurs et les cœurs du PC sur lequel s’exécute ce code, la classe System.Threading.Tasks.Parallel du Framework .NET permet de simplifier l’implémentation de ce traitement de manière parallèle, avec les nombreuses méthodes statiques surchargées qu’elle propose (For, ForEach et Invoke), sans avoir à manipuler les threads. Dans notre exemple, étant donné que nous parcourons une liste de fichiers, nous pouvons utiliser Parallel.ForEach :

try
{
    // Variables locales.
    DirectoryInfo oRepertoireSource, oRepertoireDestination;
    List<FileInfo> oListeFichiers;
    Stopwatch oStopWatch;
    int iNombreFichiersCopies;
    int iNombreThreads, iNombreThreadsMax;
    string sResult;

    // Initialisation.
    oRepertoireSource = new DirectoryInfo(TxtRepertoireSource.Text);
    oRepertoireDestination = new DirectoryInfo(TxtRepertoireDestination.Text);
    oListeFichiers = oRepertoireSource.GetFiles().ToList();
    oStopWatch = new Stopwatch();
    iNombreFichiersCopies = 0;
    iNombreThreadsMax = 0;
    iNombreThreads = 0;

    // Suppression de fichiers dans le répertoire de destination.
    foreach (FileInfo oFichier in oRepertoireDestination.GetFiles())
    {
        oFichier.Delete();
    }

    oStopWatch.Start();

    // Copie des fichiers parallélisés.
    Parallel.ForEach(oListeFichiers, delegate(FileInfo oFichier)
    {
        // Copie du fichier.
        oFichier.CopyTo(TxtRepertoireDestination.Text + @"" + oFichier.Name);

        // Progression.
        BgwCopieFichiersParallel.ReportProgress(iNombreFichiersCopies * 100 / oListeFichiers.Count);
        iNombreFichiersCopies++;
                   
        // Pour ne garder que ne plus grand nombre de threads dans le process.
        if (Process.GetCurrentProcess().Threads.Count > iNombreThreads)
        {
            iNombreThreadsMax = Process.GetCurrentProcess().Threads.Count;
        }
    });

    sResult = iNombreFichiersCopies.ToString() + "|" + (oStopWatch.Elapsed.TotalSeconds).ToString("0.00") + "|" + iNombreThreadsMax.ToString();
    Debug.WriteLine(sResult);
}
catch (Exception aEx)
{
    MessageBox.Show(aEx.Message);
}

Sur mon PC, pour réaliser une copie de 5000 fichiers de 7Ko, le programme indique qu’il a les copiés en 18 secondes avec 15 threads (de manière parallèle).

Faut-il utiliser la programmation parallèle pour tous les traitements de données ? Dans certains cas, son utilisation peut avoir l’effet inverse : pénaliser les performances d’exécution d’une tache. Le temps passé à initialiser le moteur d’exécution parallèle et à créer de nouveaux threads n’est pas compensé par le temps passé à la « parallélisation » de l’exécution de tâches. Il n’est alors pas intéressant de paralléliser l’exécution de tous les traitements répétitifs de données.

About: James RAVAILLE

Travaillant avec la plateforme Microsoft .NET depuis 2002, j’alterne les missions de formation et d’ingénierie avec cette plateforme. J’écris ce blog pour transmettre mes connaissances à tout développeur, qu’il soit débutant ou expérimenté.