Matthieu Mezil
L’objet, c’est beau
Le développement orienté objet peut être apparenté, osons le mot, à une forme d’art. Cependant, avant de devenir un « artiste » de l’objet, il y a plusieurs notions à maîtriser. A travers cet article, nous allons en aborder quelques-unes.
Par Matthieu Mezil publié le 27/04/2009 à 07:10, lu 2235 fois, 10 pages
 5 | Multi threading
A ce niveau, tous les besoins fonctionnels sont couverts. Cependant… le traitement de très gros fichiers peut prendre du temps et pendant ce temps, l’interface graphique est figée avec possibilité d’avoir des messages « l’application ne répond pas ».
Pour éviter cela, il est conseillé de lancer le traitement dans un deuxième thread. Cependant, il y a une contrainte forte : le deuxième thread ne peut pas accéder aux contrôles graphiques en WinForms. Depuis le framework 2.0, la classe BackgroundWorker simplifie la gestion des threads. Il est possible de passer au deuxième thread un argument (de type object). Dans un souci constant de factorisation, nous allons gérer ce BackgroundWorker dans la couche métier.
Certains puristes n’ aiment pas utiliser la classe BackgroundWorker qui masque la gestion des Threads et qui impose une référence au namespace System.ComponentModel dans la couche métier. Cependant, au vu de la facilité d’utilisation de cette classe, je trouve que, sorti de cas particuliers, il est souvent intéressant d’utiliser la classe BackgroundWorker.
La classe FileTransform devient donc :

public static class FileTransform

{

    public static void Transform(string inFilePath, string outFilePath, IStringsTransform traitment,

        Action<string> showError, Func<string, bool> showErrorWithRetry, Action<bool> traitmentDone)

    {

        if (!File.Exists(inFilePath))

            FileNotFoundShowError(inFilePath, showError);

        else if (!Directory.Exists(Path.GetDirectoryName(outFilePath)))

            DirectoryNotFoundShowError(outFilePath, showError);

        else

        {

            var bg = new BackgroundWorker();

            bg.DoWork += (s, e) =>

            {

                e.Result = false;

                while (true)

                {

                    try

                    {

                        FileUtil.WriteLines(outFilePath, traitment.Do(FileUtil.GetLines(inFilePath)));

                        e.Result = true;

                    }

                    catch (FileNotFoundException)

                    {

                        FileNotFoundShowError(inFilePath, showError);

                    }

                    catch (DirectoryNotFoundException)

                    {

                        DirectoryNotFoundShowError(outFilePath, showError);

                    }

                    catch (IOException)

                    {

                        if (showErrorWithRetry(string.Format(ErrorResources.IOException, outFilePath)))

                            continue;

                    }

                    catch (Exception ex)

                    {

                        showError(string.Format(ErrorResources.FileNotFoundException,

                            string.Concat(ex.GetType(), " : ", ex.Message)));

                    }

                    break;

                }

            };

            bg.RunWorkerCompleted += (s, e) =>

            {

                if (traitmentDone != null)

                    traitmentDone((bool)e.Result);

            };

            bg.RunWorkerAsync();

        }

    }

 

    private static void DirectoryNotFoundShowError(string directoryPath, Action<string> showError)

    {

        showError(string.Format(ErrorResources.DirectoryNotFoundException, Path.GetDirectoryName(directoryPath)));

    }

 

    private static void FileNotFoundShowError(string filePath, Action<string> showError)

    {

        showError(string.Format(ErrorResources.FileNotFoundException, filePath));

    }

}

Un point très intéressant de cette solution est le fait que les traitements vont être exécutés en parallèle. Ceci pose en revanche un problème : imaginons que l’on veuille lancer un batch avec une suite de traitement et que certains traitements s’appliquent sur le résultat d’un traitement précédent. Pour cela, il va falloir déterminer si le traitement peut s’effectuer en parallèle et sinon attendre l’exécution du traitement.

public static class FileTransform

{

    private static Dictionary<string, BackgroundWorker> _traitmentsRunning = new Dictionary<string, BackgroundWorker>();

    private static object _lockObject = new object();

    private static object _bwLockObject = new object();

 

    public static void Transform(string inFilePath, string outFilePath, IStringsTransform traitment,

        Action<string> showError, Func<string, bool> showErrorWithRetry, Action<bool> traitmentDone)

    {

        if (!Directory.Exists(Path.GetDirectoryName(outFilePath)))

            DirectoryNotFoundShowError(outFilePath, showError);

        else

        {

            lock (_lockObject)

            {

                if (!(File.Exists(inFilePath) || _traitmentsRunning.ContainsKey(inFilePath)))

                    FileNotFoundShowError(inFilePath, showError);

                else

                {

                    var bw = new BackgroundWorker();

                    bw.DoWork += (s, e) =>

                    {

                        e.Result = false;

                        while (true)

                        {

                            try

                            {

                                FileUtil.WriteLines(outFilePath, traitment.Do(FileUtil.GetLines(inFilePath)));

                                e.Result = true;

                            }

                            catch (FileNotFoundException)

                            {

                                FileNotFoundShowError(inFilePath, showError);

                            }

                            catch (DirectoryNotFoundException)

                            {

                                DirectoryNotFoundShowError(outFilePath, showError);

                            }

                            catch (IOException)

                            {

                                if (showErrorWithRetry(string.Format(ErrorResources.IOException, outFilePath)))

                                    continue;

                            }

                            catch (Exception ex)

                            {

                                showError(string.Format(ErrorResources.FileNotFoundException,

                                    string.Concat(ex.GetType(), " : ", ex.Message)));

                            }

                            break;

                        }

                        _traitmentsRunning.Remove(outFilePath);

                    };

                    bw.RunWorkerCompleted += (s, e) =>

                    {

                        if (traitmentDone != null)

                            traitmentDone((bool)e.Result);

                    };

 

                    StartBWAndCache(inFilePath, outFilePath, bw);

                }

            }

        }

    }

 

    private static void StartBWAndCache(string inFilePath, string outFilePath, BackgroundWorker bw)

    {

        lock (_bwLockObject)

        {

            string filePath = null;

            if (_traitmentsRunning.ContainsKey(inFilePath))

                filePath = inFilePath;

            else if (_traitmentsRunning.ContainsKey(outFilePath))

                filePath = outFilePath;

            if (filePath != null)

                _traitmentsRunning[filePath].RunWorkerCompleted +=

                    (s, e) => StartBWAndCache(inFilePath, outFilePath, bw);

            else

            {

                _traitmentsRunning.Add(outFilePath, bw);

                bw.RunWorkerAsync();

            }

        }

    }

 

    private static void DirectoryNotFoundShowError(string directoryPath, Action<string> showError)

    {

        showError(string.Format(ErrorResources.DirectoryNotFoundException, Path.GetDirectoryName(directoryPath)));

    }

 

    private static void FileNotFoundShowError(string filePath, Action<string> showError)

    {

        showError(string.Format(ErrorResources.FileNotFoundException, filePath));

    }

}

Pourquoi les deux locks ?
Le but est de s’assurer que si on lance un traitement T2 après un traitement T1, le traitement T2 sera bien exécuté après le traitement T1 dans le cas où T2 utiliserait le fichier créé par T1.
 
» Démarrer une discussion
 
Discussion démarée par killscores le 28/04/2009 à 17:06, 3 commentaire(s).