IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C# Discussion :

Parallélisme et progress


Sujet :

C#

  1. #1
    Membre régulier
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    244
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations forums :
    Inscription : Mars 2007
    Messages : 244
    Points : 122
    Points
    122
    Par défaut Parallélisme et progress
    Salut,

    Soit une tâche statique "public static async Task<ObservableCollection<Pt>> ImportPointAsync(int srid, string mdbPath, IProgress<int> progress)"
    Cette tâche crée des points suite à la lecture de 2 tables.
    Comment fonctionne-t-elle actuellement ? Elle commence par charger les tables dans 2 DataTables puis, ligne par ligne, crée les points.
    La tâche fonctionne, mais vous comprenez que créer un par un plus de 60 000 point prend un peu de temps.
    Voici la partie de code qui m'intéresse (que je voudrais améliorer) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
                ObservableCollection<Pt> points = new ObservableCollection<Pt>();
                int totalCount = dtPoints.Rows.Count;
                int processCount = await Task.Run<int>(() =>
                {
                    int count = 0;
                    foreach (DataRow drPoint in dtPoints.Rows)
                    {
                        if (drPoint != null)
                        {
                            DataRow[] drsPCodes = dtPCodes.Select("IdPoint = " + drPoint["IdPoint"]);
                            List<int> lstPCodes = new List<int>();
                            foreach (DataRow drPCode in drsPCodes)
                            {
                                int pcode = 0;
                                if (drPCode != null && int.TryParse(drPCode["PCode"].ToString(), out pcode))
                                    lstPCodes.Add(pcode);
                            }
                            Pt newPoint = new Pt(drPoint["nomPoint"].ToString(), (double)drPoint["PointX"], (double)drPoint["PointY"], lstPCodes, srid);
                            if (newPoint != null && !points.Contains(newPoint))
                                points.Add(newPoint);
                        }
                        if (progress != null)
                        {
                            progress.Report((count * 100 / totalCount));
                        }
                        count++;
                    }
     
     
                    return count;
                });
                if (points != null && points.Count > 0)
                    return points;
                return null;
    Que voudrais-je faire ? Paralléliser la création de points, mais c'est là que je coince depuis plusieurs jours et donc que je fais appel à vous.

    J'avais pensé à faire une liste des taches dont chaque tâche créerait 1000 points. Si déterminer les index de départ et de fin de lecture pour chaque tâche parallèle ne pose pas de problème (mais est-ce vraiment la meilleure solution ?), je voudrais que la tâche de base (ImportPointAsync) retourne la progression globale (en calculant le nombre de points créés en fonction du nombre de points que chaque tâche devrait retourner (comment faire cela et le gérer ?) et du total des lignes de la table dtpoints).

    Merci de vos z'avis z'avisés.
    Il n'y a pas de problèmes. Il n'y a que des solutions.
    Malheureusement, elles sont parfois un peu dur à trouver ...


    Aucune touche n'a été maltraitée pour réaliser ce texte.

  2. #2
    Membre expert
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Octobre 2013
    Messages
    1 563
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : Industrie

    Informations forums :
    Inscription : Octobre 2013
    Messages : 1 563
    Points : 3 404
    Points
    3 404
    Par défaut
    Bonjour,

    Je ne m'y connais pas trop en parallélisme et tout, donc je prend le problème différemment : à quoi servent ces 60k points?

    Si c'est pour de l'affichage on peut considérer que l’entièreté des points n'est pas nécessaire tout de suite (on peu donc charger les points en arrière plant et rafraîchir régulièrement l'affichage)

  3. #3
    Membre régulier
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    244
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations forums :
    Inscription : Mars 2007
    Messages : 244
    Points : 122
    Points
    122
    Par défaut
    Malheureusement non. Ils ne seront pas affichés mais doivent tous être créés car ils forment des polygones qui doivent être vérifiés.
    Merci quand-même de l'intérêt que tu portes à mon problème.
    Il n'y a pas de problèmes. Il n'y a que des solutions.
    Malheureusement, elles sont parfois un peu dur à trouver ...


    Aucune touche n'a été maltraitée pour réaliser ce texte.

  4. #4
    Expert confirmé

    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Septembre 2006
    Messages
    3 580
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Chef de projet NTIC
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Septembre 2006
    Messages : 3 580
    Points : 5 195
    Points
    5 195
    Par défaut
    Salut

    la solution de découper en plusieurs sous tache est une bonne idée

    pour la progression, regarde ce thread : http://stackoverflow.com/questions/1...rallel-foreach

    et sinon, tu peux surement améliorer en évitant quelques "erreurs"

    Si tu connais la taille de ta liste, tu peux dès la création de ta liste qui contiendra les données lui donner sa taille (genre new List(60000);
    tu gagneras du temps d'allocation mémoire pour commencer

    ensuite, tu fais souvent dans ton code : points.contains() ==> Ca coute cher aussi ça.. je peux comprendre que tu veuilles éviter d'éventuelle doublon, mais à ce moment
    là, peut-être qu'un dictionary et ou une table de hash te fera aussi gagner du temps
    The Monz, Toulouse
    Expertise dans la logistique et le développement pour
    plateforme .Net (Windows, Windows CE, Android)

  5. #5
    Membre régulier
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    244
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations forums :
    Inscription : Mars 2007
    Messages : 244
    Points : 122
    Points
    122
    Par défaut
    Merci beaucoup theMonz31,
    J'avais trouvé "tout seul comme un grand" que les contains ralentissaient beaucoup.
    En les enlevant et en remplaçant le return par un Distinct de la list à retourner, je me suis rendu compte que je passais de 44 sec à ... 5.6 sec !!
    Par contre je ne savais pas que j'accélérerais en créant les listes avec leur taille finale (si connue bien entendu, mais dans ce cas ci c'est connu), et je ne connaissais pas du tout le Parallel.ForEach que j'avais donc "réinventé" (mais en moins bien évidemment). Grâce au Parallel.ForEach dans un await Task.Run, j'ai aussi pu gérer la progression.

    Pour ceux que cela intéressent, voici le code modifié suite aux précieux conseils de theMonz31 (Je vous fais grâce du début de la méthode qui charge la BDD dans des DataTables. Ce n'est pas l'objet de ma question de départ et j'imagine que tout le monde sait le faire)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
                if (dtPoints == null || dtPoints.Rows.Count == 0 || dtPCodes == null || dtPCodes.Rows.Count == 0)                return null;
                List<Pt> points = new List<Pt>(dtPoints.Rows.Count);
                int totalCount = dtPoints.Rows.Count, count = 1;
                Object obj = new Object();
                await Task.Run (() => 
                    Parallel.ForEach(dtPoints.AsEnumerable(), (drPoint) =>
                        {
                            if (drPoint != null)
                            {
                                DataRow[] drsPCodes = dtPCodes.Select("IdPoint = " + drPoint["IdPoint"]);
                                List<int> lstPCodes = new List<int>();
                                foreach (DataRow drPCode in drsPCodes)
                                {
                                    int pcode = 0;
                                    if (drPCode != null && int.TryParse(drPCode["PCode"].ToString(), out pcode))
                                        lstPCodes.Add(pcode);
                                }
                                Pt newPoint = new Pt(drPoint["nomPoint"].ToString(), (double)drPoint["PointX"], (double)drPoint["PointY"], lstPCodes, srid);
                                if (newPoint != null)
                                    points.Add(newPoint);
                            }
                            lock (obj)
                            {
                                if (progress != null)
                                {
                                    count++;
                                    progress.Report((count * 100 / totalCount));
                                }
                            }
                        })
                    );
                if (points != null && points.Count > 0)
                    return new ObservableCollection<Pt>(points.Distinct());
                return null;
    Il n'y a pas de problèmes. Il n'y a que des solutions.
    Malheureusement, elles sont parfois un peu dur à trouver ...


    Aucune touche n'a été maltraitée pour réaliser ce texte.

  6. #6
    Membre chevronné
    Homme Profil pro
    edi
    Inscrit en
    Juin 2007
    Messages
    898
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

    Informations professionnelles :
    Activité : edi

    Informations forums :
    Inscription : Juin 2007
    Messages : 898
    Points : 1 915
    Points
    1 915
    Par défaut
    Citation Envoyé par Jean-Marc68 Voir le message
    Par contre je ne savais pas que j'accélérerais en créant les listes avec leur taille finale (si connue bien entendu, mais dans ce cas ci c'est connu)
    Les conteneurs dynamiques comme les List gèrent souvent une taille (le nombre d'objets stockés) et une capacité (le nombre d'emplacements disponibles). Quand on veut ajouter un objet alors que la taille atteint déjà la capacité le conteneur, pour s'adapter, va réserver une nouvelle zone mémoire plus grande (généralement le double), recopier la première zone mémoire, la libérer, puis ajouter le nouvel item sur cette nouvelle zone. Si tu connais à l'avance le nombre d'objets à contenir tu peux demander à ta List de réserver directement la capacité dont elle a besoin et éviter cette effet de dépassement de seuil.

  7. #7
    Membre régulier
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Novembre 2013
    Messages
    51
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2013
    Messages : 51
    Points : 72
    Points
    72
    Par défaut
    Tu devrais utiliser System.Threading.Tasks.Parallel.ForEach() en passant à ce ForEach une liste d'étendues de points à calculer.

    Par contre rafraichi l'IHM en appelant Dispatcher.BeginInvoke() ou Dispatcher.Invoke() pour ne pas avoir de problème.

  8. #8
    Membre régulier
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    244
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations forums :
    Inscription : Mars 2007
    Messages : 244
    Points : 122
    Points
    122
    Par défaut
    Merci Noxen,
    C'est toujours intéressant d'apprendre le pourquoi du comment. Ça permet après de savoir comment, mais surtout pourquoi faire autrement.
    Il n'y a pas de problèmes. Il n'y a que des solutions.
    Malheureusement, elles sont parfois un peu dur à trouver ...


    Aucune touche n'a été maltraitée pour réaliser ce texte.

  9. #9
    Membre régulier
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    244
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations forums :
    Inscription : Mars 2007
    Messages : 244
    Points : 122
    Points
    122
    Par défaut
    Dzedia, je ne comprend pas très bien ce que tu veux dire par "Tu devrais utiliser System.Threading.Tasks.Parallel.ForEach() en passant à ce ForEach une liste d'étendues de points à calculer."
    En ce qui concerne la modification de l'IHM, je retourne un IProgress car cette méthode se situe dans une bibliothèque que je suis en train de créer.
    L'IHM qui consommera la bibliothèque pourra donc gérer la progression retournée par l'IProgress comme elle l'entend (si j'ai bien compris ce que tu voulais dire à propos de l'invocation).
    Il n'y a pas de problèmes. Il n'y a que des solutions.
    Malheureusement, elles sont parfois un peu dur à trouver ...


    Aucune touche n'a été maltraitée pour réaliser ce texte.

  10. #10
    Membre régulier
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Novembre 2013
    Messages
    51
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2013
    Messages : 51
    Points : 72
    Points
    72

  11. #11
    Membre régulier
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    244
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations forums :
    Inscription : Mars 2007
    Messages : 244
    Points : 122
    Points
    122
    Par défaut
    J'ai lu cette page mais je ne comprend pas ce que je fais mal (et donc pourquoi je le fais mal, et donc comment le corriger). Est-ce la ligne Parallel.ForEach(dtPoints.AsEnumerable(), (drPoint) => qui n'est pas correcte ?
    Aussi, si je peux utiliser l'invocation dans la bibliothèque, comment dois-je faire ?
    J'ai modifié mon code comme ceci, mais les 2 await successif me laissent douter que mon code n'est pas correct.
    De plus, avec la ligne "await System.Windows.Application.Current.Dispatcher.Invoke(async () =>", le code compile mais ne passe plus, j'ai une erreur :
    "Une exception de type 'System.NullReferenceException' s'est produite dans mscorlib.dll mais n'a pas été gérée dans le code utilisateur
    Informations supplémentaires : La référence d'objet n'est pas définie à une instance d'un objet."
    Ça me confirme bien qu'il y a un pbl, mais je ne trouve toutefois pas la solution idéale avec l'invocation.
    Mais est-ce vraiment nécessaire dans une méthode de type "public static async Task<ObservableCollection<Pt>> ImportPointAsync(int srid, string mdbPath, IProgress<int> progress)" ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
                if (dtPoints == null || dtPoints.Rows.Count == 0 || dtPCodes == null || dtPCodes.Rows.Count == 0)
                    return null;
                List<Pt> points = new List<Pt>(dtPoints.Rows.Count);
                int totalCount = dtPoints.Rows.Count, count = 1;
                Object obj = new Object();
                await System.Windows.Application.Current.Dispatcher.Invoke(async () =>
                {
                   await Task.Run(() =>
                   Parallel.ForEach(dtPoints.AsEnumerable(), (drPoint) =>
                       {
                           // The more computational work you do here, the greater 
                           // the speedup compared to a sequential foreach loop.
                           if (drPoint != null)
                           {
                               DataRow[] drsPCodes = dtPCodes.Select("IdPoint = " + drPoint["IdPoint"]);
                               List<int> lstPCodes = new List<int>();
                               foreach (DataRow drPCode in drsPCodes)
                               {
                                   int pcode = 0;
                                   if (drPCode != null && int.TryParse(drPCode["PCode"].ToString(), out pcode))
                                       lstPCodes.Add(pcode);
                               }
                               Pt newPoint = new Pt(drPoint["nomPoint"].ToString(), (double)drPoint["PointX"], (double)drPoint["PointY"], lstPCodes, srid);
                               if (newPoint != null)
                                   points.Add(newPoint);
                           }
                           lock (obj)
                           {
                               if (progress != null)
                               {
                                   count++;
                                   progress.Report((count * 100 / totalCount));
                               }
                           }
                       })
                    );
                });
                if (points != null && points.Count > 0)
                    return new ObservableCollection<Pt>(points.Distinct());
                return null;
    Il n'y a pas de problèmes. Il n'y a que des solutions.
    Malheureusement, elles sont parfois un peu dur à trouver ...


    Aucune touche n'a été maltraitée pour réaliser ce texte.

  12. #12
    Membre chevronné
    Homme Profil pro
    edi
    Inscrit en
    Juin 2007
    Messages
    898
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

    Informations professionnelles :
    Activité : edi

    Informations forums :
    Inscription : Juin 2007
    Messages : 898
    Points : 1 915
    Points
    1 915
    Par défaut
    Dispatcher.Invoke permet dans une application fenêtrée d'accéder au thread de l'interface depuis un autre thread (sinon la compartimentation des thread provoquerait une exception). Mais l'usage d'async/await fait que le compilateur prend en charge pas mal de choses. Je n'ai pas tous les détails en tête, il me faudrait relire les différents articles sur le sujet. François Dorin est le spécialiste du sujet sur developpez.com, il pourra clarifier les choses s'il passe sur ce fil de discussion et qu'il a le temps.

  13. #13
    Membre confirmé
    Avatar de nouanda
    Homme Profil pro
    Hobbyist
    Inscrit en
    Mai 2002
    Messages
    246
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Australie

    Informations professionnelles :
    Activité : Hobbyist

    Informations forums :
    Inscription : Mai 2002
    Messages : 246
    Points : 627
    Points
    627
    Par défaut
    J'espère ne pas dire de bêtise, mais je crois bien que les objets List<T> ne sont pas thread-safe (en fait, j'en suis sûr).
    La commande points.Add(newPoint); va subir une race condition entre les threads générés par le Parallel.ForEach, et il y a un risque de ne pas retourner les bonnes valeurs. Et comme c'est la base du retour de ta fonction, autant le signaler.
    Il vaut mieux se tourner vers un ImmutableList<T>, qui est thread-safe, ou implémenter une gestion de la synchronisation (Immutable, c'est bien...)

    Et en ayant dit cela, je n'aide pas sur le progress, mais je sauve peut-être certaines de tes données...
    " Entre le Savoir et le Pouvoir, il y a le Vouloir "

    Desole pour les accents, je suis en QWERTY...

  14. #14
    Membre régulier
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Novembre 2013
    Messages
    51
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2013
    Messages : 51
    Points : 72
    Points
    72
    Par défaut
    En plus de ce qui est dit plus haut :

    A mon avis le Task.Run est en trop. Le Parallel.Foreach() n'en a pas besoin.

    Ensuite Dispatcher.Invoke doit être appelé directement sans await. Il permet de mettre à jour l'affichage dans le thread d'affichage justement, et non dans le thread créé par Parallel.Foreach().

    Si tu veux attendre la fin de la maj de l'affichage (du point), appels Dispatcher.Invoke. En revanche si tu n'a pas besoin d'attendre la fin de l'affichage du point, appels Dispatcher.BeginInvoke (version asynchrone de Invoke).

    Ça devrait donner un truc comme ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
                List<Pt> points = new List<Pt>(dtPoints.Rows.Count);
                int totalCount = dtPoints.Rows.Count, count = 1;
     
                System.Threading.Tasks.Parallel.ForEach(dtPoints.Rows.OfType<System.Data.DataRow>().ToList(), (drPoint) =>
                {
                    // The more computational work you do here, the greater 
                    // the speedup compared to a sequential foreach loop.
                    if (drPoint != null)
                    {
                        System.Data.DataRow[] drsPCodes = dtPCodes.Select("IdPoint = " + drPoint["IdPoint"]);
                        List<int> lstPCodes = new List<int>();
                        foreach (System.Data.DataRow drPCode in drsPCodes)
                        {
                            int pcode = 0;
                            if (drPCode != null && int.TryParse(drPCode["PCode"].ToString(), out pcode))
                                lstPCodes.Add(pcode);
                        }
                        Pt newPoint = new Pt(drPoint["nomPoint"].ToString(), (double)drPoint["PointX"], (double)drPoint["PointY"], lstPCodes, srid);
                        if (newPoint != null)
                            lock(points)
                                points.Add(newPoint);
                    }
     
                    System.Windows.Application.Current.Dispatcher.BeginInvoke(() =>
                    {
                        if (progress != null)
                        {
                            count++;
                            progress.Report((count * 100 / totalCount));
     
                            // Afficher le point ici 
                        }
                    });
                });
                if (points != null && points.Count > 0)
                    return new ObservableCollection<Pt>(points.Distinct());
                return null;
    en outre je te conseille de passer des listes de points à traiter à Parallel.Foreach() plutôt qu'un point à la fois. Tu peux aussi limiter le nb de tâches en parallel pour éviter de surcharger la machine :

    int i = 0;
    var maListeDeListesDePoints = dtPoints.Rows.OfType<System.Data.DataRow>().GroupBy(x => i++ / 5).Select(x => x.ToList()).ToList(); // Constitue des listes de 5 points
    Parallel.ForEach(maListeDeListesDePoints, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount / 2 }, listeDePoints => { ....

  15. #15
    Membre régulier
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    244
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations forums :
    Inscription : Mars 2007
    Messages : 244
    Points : 122
    Points
    122
    Par défaut
    Merci à tous pour vos différentes aides qui m'aident, mais surtout qui m'apprennent beaucoup.
    Vos explications sont tellement plus parlantes pour moi que ce que je trouve sur les sites "généraux".

    A mon avis le Task.Run est en trop. Le Parallel.Foreach() n'en a pas besoin.
    Je ne sais pas si c'est la meilleure façon de faire, mais j'ai mis le Task.Run car la méthode est un async Task qui doit retourner, entre autre, une progression.
    En effet le Parallel.Foreach() n'a pas besoin de Task.Run, mais pour pouvoir retourner la progression après chaque itération du Parallel.Foreach(), il faut que le Parallel.Foreach() soit dans un await afin que la progression puisse être retournée après chaque itération et pas après le Parallel.Foreach() complet.

    Le invoke ou begininvoke ne me paraît pas utile dans le cas présent parce que je ne change rien dans le Threat de l'UI. Comme cette méthode doit se trouver dans une bibliothèque, je ne sais pas a priori comment sera gérée la progression (le nom de la progressbar à alimenter peut être différent dans différents projets faisant appel à la bibliothèque, par exemple). Il n'y a donc pas d'affichage de point, ou de texte ou quoi que ce soit d'autre. Je retourne juste une progression et l'appelant gère la progression comme il le veut. Je ne fais donc que lui retourner une progression de l'avancée de la création des points (qui est gérée par l'UI appelante), puis la liste des points à la fin. Actuellement sur un projet test, la ProgressBar avance bien au fur et à mesure de la création des points.

    J'espère ne pas dire de bêtise, mais je crois bien que les objets List<T> ne sont pas thread-safe (en fait, j'en suis sûr).
    La commande points.Add(newPoint); va subir une race condition entre les threads générés par le Parallel.ForEach, et il y a un risque de ne pas retourner les bonnes valeurs. Et comme c'est la base du retour de ta fonction, autant le signaler.
    Il vaut mieux se tourner vers un ImmutableList<T>, qui est thread-safe, ou implémenter une gestion de la synchronisation (Immutable, c'est bien...)
    Je ne connaissais pas le ImmutableList que j'ai installé (Nuget). Par contre new ImmutableList<T>(int longueurDeLaListe) ne semble pas exister. J'ai dont bricolé
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ImmutableList<Pt> points = new List<Pt>(dtPoints.Rows.Count).ToImmutableList();
    qui ne me semble pas très propre visuellement mais qui a l'air de fonctionner.
    Je me doutait que je pouvais avoir des problèmes avec l'alimentation de ma List, c'est pourquoi j'avais fais
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
                               if (newPoint != null)
                               {
                                   lock (obj)
                                   {
                                       points.Add(newPoint);
                                   }
                               }
    car je me disais qu'en "lockant" la liste, elle ne pourrait pas être alimentée par 2 Threat en même temps, mais que les Threat devraient attendre leur tour. Peut-être étais-je dans l'erreur ou, ce à quoi je pensais, ça ralenti l'alimentation de la liste si des Threat doivent attendre.

    En conclusion, encore merci à tous de votre aide et de vos explications.
    La situation actuelle de la méthode est
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
            public static async Task<ObservableCollection<Pt>> ImportPointAsync(int srid, string mdbPath, IProgress<int> progress)
            {
                DataTable dtPoints = new DataTable();
                DataTable dtPCodes = new DataTable();
     
                --- chargement des tables à partir de la mdb ---
     
                if (dtPoints == null || dtPoints.Rows.Count == 0 || dtPCodes == null || dtPCodes.Rows.Count == 0)
                    return null;
                ImmutableList<Pt> points = new List<Pt>(dtPoints.Rows.Count).ToImmutableList();
                int totalCount = 0, count = 1, i=0;
                Object obj = new Object();
                var maListeDeListesDePoints = dtPoints.Rows.OfType<System.Data.DataRow>().GroupBy(x => i++ / 5).Select(x => x.ToList()).ToList(); // Constitue des listes de 5 points
                totalCount = maListeDeListesDePoints.Count;
                await Task.Run(() =>
                Parallel.ForEach(maListeDeListesDePoints, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount / 2 }, listeDePoints => {
                           for (int j=0; j< listeDePoints.Count; j++)
                           {
                               DataRow drPoint = listeDePoints[j];
                               DataRow[] drsPCodes = dtPCodes.Select("IdPoint = " + drPoint["IdPoint"]);
                               List<int> lstPCodes = new List<int>();
                               foreach (DataRow drPCode in drsPCodes)
                               {
                                   int pcode = 0;
                                   if (drPCode != null && int.TryParse(drPCode["PCode"].ToString(), out pcode))
                                       lstPCodes.Add(pcode);
                               }
                               Pt newPoint = new Pt(drPoint["nomPoint"].ToString(), (double)drPoint["PointX"], (double)drPoint["PointY"], lstPCodes, srid);
                               if (newPoint != null)
                                points.Add(newPoint);
                           }
                           lock (obj)
                           {
                               if (progress != null)
                               {
                                   count++;
                                   progress.Report((count * 100 / totalCount));
                               }
                           }
                       })
                    );
                if (points != null && points.Count > 0)
                    return new ObservableCollection<Pt>(points.Distinct());
                return null;
            }
    Il n'y a pas de problèmes. Il n'y a que des solutions.
    Malheureusement, elles sont parfois un peu dur à trouver ...


    Aucune touche n'a été maltraitée pour réaliser ce texte.

  16. #16
    Membre chevronné
    Homme Profil pro
    edi
    Inscrit en
    Juin 2007
    Messages
    898
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

    Informations professionnelles :
    Activité : edi

    Informations forums :
    Inscription : Juin 2007
    Messages : 898
    Points : 1 915
    Points
    1 915
    Par défaut
    Au regard de la description de ImmutableList<T> dans la msdn je suis un peu perplexe dans son usage. Est-ce-que au lieu de points.Add(newPoint); ça ne devrait pas être points = points.Add(newPoint); ? Tu obtiens bien le résultat attendu ?

  17. #17
    Membre régulier
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    244
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations forums :
    Inscription : Mars 2007
    Messages : 244
    Points : 122
    Points
    122
    Par défaut
    Merci de cette très juste observation, Noxen. Je corrige.
    Il n'y a pas de problèmes. Il n'y a que des solutions.
    Malheureusement, elles sont parfois un peu dur à trouver ...


    Aucune touche n'a été maltraitée pour réaliser ce texte.

  18. #18
    Membre chevronné
    Homme Profil pro
    edi
    Inscrit en
    Juin 2007
    Messages
    898
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

    Informations professionnelles :
    Activité : edi

    Informations forums :
    Inscription : Juin 2007
    Messages : 898
    Points : 1 915
    Points
    1 915
    Par défaut
    Ok, je prendrai le temps de décortiquer la fonction pour bien comprendre.

  19. #19
    Expert éminent sénior

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 757
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 757
    Points : 10 697
    Points
    10 697
    Billets dans le blog
    21
    Par défaut
    Bonjour,

    Citation Envoyé par Jean-Marc68 Voir le message
    Je ne connaissais pas le ImmutableList que j'ai installé (Nuget). Par contre new ImmutableList<T>(int longueurDeLaListe) ne semble pas exister. J'ai dont bricolé
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ImmutableList<Pt> points = new List<Pt>(dtPoints.Rows.Count).ToImmutableList();
    qui ne me semble pas très propre visuellement mais qui a l'air de fonctionner.
    Je me doutait que je pouvais avoir des problèmes avec l'alimentation de ma List, c'est pourquoi j'avais fais
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
                               if (newPoint != null)
                               {
                                   lock (obj)
                                   {
                                       points.Add(newPoint);
                                   }
                               }
    car je me disais qu'en "lockant" la liste, elle ne pourrait pas être alimentée par 2 Threat en même temps, mais que les Threat devraient attendre leur tour. Peut-être étais-je dans l'erreur ou, ce à quoi je pensais, ça ralenti l'alimentation de la liste si des Threat doivent attendre.
    Utiliser lock est ici la bonne approche. La ImmutableList ici est tout ce qu'il y a de contre-productif
    • ajouter énormément d'éléments un par un provoque une pression assez importante au niveau du garbage collector (beaucoup d'allocation et donc de nettoyage à faire) ;
    • c'est lent : chaque modification nécessite de construire une nouvelle liste indépendante de la première. Rajouter 1000 éléments nécessite donc de créer 1000 nouvelles listes !
    • ce n'est pas threadsafe. Qu'on s'entende bien : les appels aux méthodes de la liste le sont (puisque la liste est non modifiable, il n'y a pas de risque), mais il faut des précautions derrières si on veut que la liste soit mise à jour. Or faire un list = list.Add(item) n'est pas thread safe car il s'agit de deux instructions : appel de la méthode, et assignation de la valeur de retour !!! Il faut donc impérativement utiliser... un lock ! Du coup, l'utilisation d'une ImmutableList n'a plus de sens.



    Enfin, je modifierai à minima le code ainsi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    static async Task<ObservableCollection<Pt>> ImportPointAsync(int srid, string mdbPath, IProgress<int> progress)
            {
                DataTable dtPoints = new DataTable();
                DataTable dtPCodes = new DataTable();
     
                --- chargement des tables à partir de la mdb ---
     
                if (dtPoints == null || dtPoints.Rows.Count == 0 || dtPCodes == null || dtPCodes.Rows.Count == 0)
                    return null;
                ImmutableList<Pt> points = new List<Pt>(dtPoints.Rows.Count).ToImmutableList();
                int totalCount = 0, count = 1, i=0;
                Object obj = new Object();
                var maListeDeListesDePoints = dtPoints.Rows.OfType<System.Data.DataRow>().GroupBy(x => i++ / 5).Select(x => x.ToList()).ToList(); // Constitue des listes de 5 points
                totalCount = maListeDeListesDePoints.Count;
                await Task.Run(() =>
                Parallel.ForEach(maListeDeListesDePoints, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount / 2 }, listeDePoints => {
                           for (int j=0; j< listeDePoints.Count; j++)
                           {
                               DataRow drPoint = listeDePoints[j];
                               DataRow[] drsPCodes = dtPCodes.Select("IdPoint = " + drPoint["IdPoint"]);
                               List<int> lstPCodes = new List<int>();
                               foreach (DataRow drPCode in drsPCodes)
                               {
                                   int pcode = 0;
                                   if (drPCode != null && int.TryParse(drPCode["PCode"].ToString(), out pcode))
                                       lstPCodes.Add(pcode);
                               }
                               Pt newPoint = new Pt(drPoint["nomPoint"].ToString(), (double)drPoint["PointX"], (double)drPoint["PointY"], lstPCodes, srid);
                               if (newPoint != null)
                                points.Add(newPoint);
                           }
                           if (progress != null)
                           { 
                               int progressCount;
                               lock (obj)
                               {
                                  count++;
                                  progressCount = count;
                               }
     
                               progress.Report((progressCount * 100 / totalCount));
                           }
                       })
                    );
                if (points != null && points.Count > 0)
                    return new ObservableCollection<Pt>(points.Distinct());
                return null;
            }
    Ainsi :
    • le lock n'est réalisé que s'il est nécessaire de reporter la progression
    • la durée d'acquisition du lock est minimisée (et donc minimise le risque de blocage)
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

  20. #20
    Membre régulier
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    244
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations forums :
    Inscription : Mars 2007
    Messages : 244
    Points : 122
    Points
    122
    Par défaut
    Merci de ton avis concernant le ImmutableList et ma manière d'alimenter la List "standard" avec un lock, François.
    Avec les tests que j'avais fais il ne me manquait pas de point. Mais selon les autres avis je me disais que j'avais peut-être eu de la chance.
    La partie dont le parle
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    if (newPoint != null)
    {
        lock (obj)
        {
            points.Add(newPoint);
        }
    }
    Par contre je ne comprend pas bien ta proposition
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    if (progress != null)
    { 
         int progressCount;
         lock (obj)
         {
           count++;
           progressCount = count;
         }
         progress.Report((progressCount * 100 / totalCount));
    }
    J'avais instancié le count hors du Parallel.foreach pour que sa valeur se maintienne et qu'elle reprenne les valeurs générées par tous les foreach parallèles. J'ai mis le count++ dans le foreach pour que chaque foreach parallèle alimente le même count et je l'ai mis dans un lock pour empêcher la double alimentation simultanée. Selon mes tests ça fonctionnait bien aussi.
    Il me semble que comme le progress.Report ne fait que lire la valeur du count, donc comme le count n'a pas besoin d'être modifié il n'est pas nécessaire de mettre le progress.Report dans le lock (mais ce serait neanmoins possible).
    Pourquoi créer un int progressCount dans chaque foreach parallèle et faire lire le progressCount par le progress.Report au lieu de lui faire lire le count ?

    Merci
    Il n'y a pas de problèmes. Il n'y a que des solutions.
    Malheureusement, elles sont parfois un peu dur à trouver ...


    Aucune touche n'a été maltraitée pour réaliser ce texte.

Discussions similaires

  1. Quelqu'un connait PROGRESS?
    Par sandrine dans le forum Autres SGBD
    Réponses: 23
    Dernier message: 07/05/2004, 11h29
  2. [web] Barre de Progression ASCII
    Par Red Bull dans le forum Web
    Réponses: 13
    Dernier message: 05/06/2003, 12h56
  3. PROGRESS- Obtenir le ROWNUM, ROWID, etc?!?
    Par nmathon dans le forum Requêtes
    Réponses: 4
    Dernier message: 27/05/2003, 14h05
  4. [Progress] Odbc
    Par NewB dans le forum Autres SGBD
    Réponses: 8
    Dernier message: 26/03/2003, 09h19

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo