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 :

BackgroundWorker provoquant un freeze GUI


Sujet :

C#

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé Avatar de mathisdu42
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Mars 2013
    Messages
    168
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur .NET

    Informations forums :
    Inscription : Mars 2013
    Messages : 168
    Par défaut BackgroundWorker provoquant un freeze GUI
    Bonjour tout le monde !

    Travaillant actuellement sur un projet WinForm lié à une base de données, mon but étant de récupérer des médicaments en base afin de les
    afficher (avec tri possible), j'ai donc compris qu'il était nécessaire de récupérer le tout de manière asynchrone afin de ne pas surcharger le thread
    principal. J'ai pour habitude d'utiliser des BackgroundWorker, j'ai donc dans un premier temps créé un "poco" Medicament afin de représenter au mieux
    les tables pour en faire des objets :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
        public class Medicament
        {
            public static string Id         { get; set; }
            public string IdPrive           { get; set; }
            public string NomCommercial     { get; set; }
            public string IdFamille         { get; set; }
            public string Composition       { get; set; }
            public string Effets            { get; set; }
            public string ContreIndications { get; set; }
            public decimal Prix             { get; set; }
            public static Image Image       { get; set; }
        }
    Ensuite j'ai ce que j'appelle des "servies", le but est de splitter au maximum le code car ça m'aide à mieux m'y retrouver donc pour un service
    qui représente une fonctionnalité, j'ai une interface définissant les méthodes et une classe enfant qui se charge de les compléter comme suit
    (Petite précision : Vous verrez les appellations Produit et Medicament, c'est la même chose, petite maladresse de ma part) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
        public interface IProduitCore
        {
            List<Medicament> GetMedicaments();
            [...]
        }
    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
     
        public class ProduitCore : IProduitCore
        {
            public List<Medicament> GetMedicaments()
            {
                var medicaments = new List<Medicament>();
     
                using (var mySqlConnection = new MySqlConnection())
                {
                    mySqlConnection.ConnectionString = Generique.MySqlConnectionString;
                    mySqlConnection.Open();
     
                    const string query = @"SELECT Medicament.id, Medicament.nomCommercial, Medicament.prix
                                           FROM Medicament
                                           ORDER BY Medicament.nomCommercial ASC";
     
                    using (var mySqlCommand = new MySqlCommand(query, mySqlConnection))
                    {
                        using (var mySqlDataReader = mySqlCommand.ExecuteReader())
                        {
                            if (!mySqlDataReader.HasRows) return null;
                            while (mySqlDataReader.Read())
                            {
                                var medicament = new Medicament
                                {
                                    IdPrive       = mySqlDataReader.GetString(0),
                                    NomCommercial = mySqlDataReader.GetString(1),
                                    Prix          = mySqlDataReader.GetDecimal(2)
                                };
     
                                medicaments.Add(medicament);
                            }
     
                            return medicaments;
                        }
                    }
                }
            }
        }
    Ensuite pour afficher tous ces objets, j'ai fait un UserControl qui sera chargé d'afficher les informations des objets le tout dans un FlowLayoutPanel
    donc rien de particulier j'y déclare simplement les attributs dont j'ai besoin. C'est l'étape suivante qui me pose problème, j'ai donc fait un BackgroundWorker
    qui, au lancement du formulaire, va afficher un spinner de chargement puis dans le même temps va charger la liste des médicaments puis dessiner les
    userControls dans le flPanel.


    Initialisation et exécution du backgroundWorker :
    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
     
            private void frmAccueil_Load(object sender, EventArgs e)
            {
                _bgwGetAll = new BackgroundWorker { WorkerSupportsCancellation = true };
                if (!_bgwGetAll.IsBusy)
                {
                    fLPanelProduits.Controls.Clear();
     
                    tabControlAccueil.SelectedTab = tabPageChargement;
     
                    _produitCore     = new ProduitCore();
     
                    _bgwGetAll.DoWork             += _bgwGetAll_DoWork;
                    _bgwGetAll.RunWorkerCompleted += _bgwGetAll_RunWorkerCompleted;
                    _bgwGetAll.RunWorkerAsync();
                }
            }
    Code exécuté par le backgroundWorker
    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
     
            private void _bgwGetAll_DoWork(object sender, DoWorkEventArgs e)
            {
                _medicaments = _produitCore.GetMedicaments();
     
                if (_medicaments != null)
                {
                    foreach (var items in _medicaments)
                    {
                        var ucProduit = new ucProduit();
                        ucProduit.PicProduit.Tag      = items.IdPrive;
                        ucProduit.LblNomProduit.Text  = items.NomCommercial;
                        ucProduit.LblPrixProduit.Text = $@"{items.Prix:00.00} € TTC";
     
                        fLPanelProduits.BeginInvoke((MethodInvoker)delegate
                        {
                            fLPanelProduits.Controls.Add(ucProduit);
                        });
                    }
                }
                else
                {
                    Composant.MessageErreurProduits("Aucun produit n'a pu être trouvé pour cette sélection.", fLPanelProduits);
                }
            }
    Et c'est ici que le problème se pose, ma liste se charge bien, les médicaments s'affichent tous bien dans les userControls, le seul souci
    c'est que durant l'exécution du bgw, ma form freeze ce qui est très étrange puisque j'utilise ce même procédé ailleurs dans mon application
    et le problème ne se pose pas !

    Désolé pour la longueur du post, en espérant trouver une petite aide.
    Merci d'avance

    Cordialement.

  2. #2
    Membre émérite
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2010
    Messages
    479
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France

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

    Informations forums :
    Inscription : Août 2010
    Messages : 479
    Par défaut
    Bonjour, si tu prépares ta collection de contrôles a ajouter dans une List<ucProduit> :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    var ctrls = new List<ucProduit>();
                    foreach (var items in _medicaments)
                    {
                        var ucProduit = new ucProduit();
                        ucProduit.PicProduit.Tag = items.IdPrive;
                        ucProduit.LblNomProduit.Text = items.NomCommercial;
                        ucProduit.LblPrixProduit.Text = $@"{items.Prix:00.00} € TTC";
                        ctrls.Add(ucProduit);
                    }
    et que tu fais un seul appel à ton formulaire avec un AddRange :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    fLPanelProduits.BeginInvoke((MethodInvoker)delegate
                    {
                        fLPanelProduits.Controls.AddRange(ctrls.ToArray());
                    });
    Que se passe t'il ?

    Après la "nouvelle" manière de faire c'est plutôt à l'aide des async et await pour les tâches qui prennent du temps.

  3. #3
    Membre confirmé Avatar de mathisdu42
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Mars 2013
    Messages
    168
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur .NET

    Informations forums :
    Inscription : Mars 2013
    Messages : 168
    Par défaut
    Merci pour ta réponse.

    J'ai testé ce que tu m'as proposé, ça me semblait être une solution intéressante mais hélas le résultat est le même.
    J'ai tenté un debug pas à pas en restant appuyé sur F10, je peux voir le spinner bouger jusqu'au moment ou il doit ajouter
    les userControls dans le flowLayoutPanel, le formulaire freeze (j'ai essayé avec mon code et celui que tu m'as suggéré).

    Concernant async et await j'y ai pensé mais étant donné que je ne récupère pas énormément de données (moins de 150 lignes je dirais),
    j'ai pensé que le bgw ferait l'affaire. Je vais tout de même me pencher sur cette pratique mais c'est très étrange car dans ce même formulaire,
    je récupère un historique de commandes, exactement de la même manière (userControls, flowLayoutPanel) et aucun souci.

  4. #4
    Expert éminent Avatar de Pol63
    Homme Profil pro
    .NET / SQL SERVER
    Inscrit en
    Avril 2007
    Messages
    14 198
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : .NET / SQL SERVER

    Informations forums :
    Inscription : Avril 2007
    Messages : 14 198
    Par défaut
    quand ca freeze tu peux cliquer sur pause dans visual studio
    ensuite dans la zone de saisie de VS en haut tu tapes "threads" et tu ouvres la fenetre des threads CPU
    sur chaque thread tu peux double cliquer et voir la pile des appels de celui ci
    tu verras donc ce que fait le thread principal et où il attend
    tu peux aussi regarder où sont les autres threads tu pourras peut etre en déduire d'où vient le problème (je mise sur le begin invoke et le uc qui est créé sur un thread secondaire)

    le bgw a une méthode reportprogress qui peut etre utilisée pour autre chose que de la progression
    tout comme l'event completed on peut transmettre des données et c'est exécuté sur le thread principal
    le bgw ne devrait donc que lire les données, puis les passer au progress depuis le for each pour qu'il créé les uc
    ou encore passer la collection obtenue directement au completed, la création des ucs n'est pas longue

    async await ca fait la même chose que le bgw, c'est juste un peu plus compliqué à comprendre et plus simple à utiliser quand il en faut beaucoup
    Cours complets, tutos et autres FAQ ici : C# - VB.NET

  5. #5
    Membre confirmé Avatar de mathisdu42
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Mars 2013
    Messages
    168
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur .NET

    Informations forums :
    Inscription : Mars 2013
    Messages : 168
    Par défaut
    Citation Envoyé par Pol63 Voir le message
    quand ca freeze tu peux cliquer sur pause dans visual studio
    ensuite dans la zone de saisie de VS en haut tu tapes "threads" et tu ouvres la fenetre des threads CPU
    sur chaque thread tu peux double cliquer et voir la pile des appels de celui ci
    tu verras donc ce que fait le thread principal et où il attend
    tu peux aussi regarder où sont les autres threads tu pourras peut etre en déduire d'où vient le problème (je mise sur le begin invoke et le uc qui est créé sur un thread secondaire)

    le bgw a une méthode reportprogress qui peut etre utilisée pour autre chose que de la progression
    tout comme l'event completed on peut transmettre des données et c'est exécuté sur le thread principal
    le bgw ne devrait donc que lire les données, puis les passer au progress depuis le for each pour qu'il créé les uc
    ou encore passer la collection obtenue directement au completed, la création des ucs n'est pas longue

    async await ca fait la même chose que le bgw, c'est juste un peu plus compliqué à comprendre et plus simple à utiliser quand il en faut beaucoup
    Bien vu de ta part ! Alors j'ai deux infos à remonter de la pile d'appels. Le résultat de mon premier test indique que tu as raison, le moment où se passe le freeze
    est cette partie :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
                        fLPanelProduits.BeginInvoke((MethodInvoker)delegate
                        {
                            fLPanelProduits.Controls.Add(ucProduit);
                        });
    Par curiosité, j'ai aussi voulu reproduire exactement le même code que pour la fonctionnalité "historique des commandes", qui elle fonctionne et je me suis rendu compte
    que le flowLayout de l'historique est crée dynamiquement. J'ai donc fait pareil pour les médicaments, j'ai supprimé le flowLayout "physique" puis fait un dynamique que j'appelle
    dans le constructeur.

    Étonnamment, lorsque j'effectue un second test, le thread principal attend cette fois-ci dans mon RunWorkerCompleted (effectivement j'ai omis de le mentionner) comme suit :

    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
     
            private void _bgwGetAll_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                if (e.Cancelled)
                {
                    MessageBox.Show(@"L'opération a été annulée par l'utilisateur", @"Interruption", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }
                else if (e.Error != null)
                {
                    MessageBox.Show($@"Erreur : {e.Error.Message}", @"Erreur", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }
                if (_closePending) Close();
                _closePending = false;
                // IL ATTEND A LA LIGNE CI-DESSOUS
                tabControlAccueil.SelectedTab = tabPageProduits;
            }
    J'ai un peu de mal à saisir la raison pour laquelle il bloquerait sur le changement de tabPage.
    Le scénario que je fais d'habitude c'est :
    Affichage de la tabPage de chargement -> Exécution du bgw -> Fin du bgw, affichage de la tabPage des produits.

    J'ai essayé de passer la collection dans mon RunWorkerCompleted mais c'est toujours du freeze

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

    Informations professionnelles :
    Activité : edi

    Informations forums :
    Inscription : Juin 2007
    Messages : 941
    Par défaut
    Pol63 a dit l'essentiel. Pour un exemple de gestionnaire d'événement async :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private async void frmAccueil_Load(object sender, EventArgs e)
    {
      DisableButtons(); // désactiver les boutons de l'interface
      var produits = await Task.Run(() => new ProductCore().GetMedicament()).ConfigureAwait(true); // appel asynchrone pour libérer le thread courant
      for(int i = 0; i < produits.Length; ++i)
      {
        AddItemToPanel(produits[i]); // ajout du produit au panel de la fenêtre
        if(i + 1 % 10 == 0) await Task.Delay(100).ConfigureAwait(true); // temporisation d'un dixième de seconde tous les 10 items afin de ne pas saturer l'UI
      }
      EnableButtons(); // on réactive les éléments de l'UI
    }

  7. #7
    Membre confirmé Avatar de mathisdu42
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Mars 2013
    Messages
    168
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur .NET

    Informations forums :
    Inscription : Mars 2013
    Messages : 168
    Par défaut
    Merci beaucoup à tous les trois !

    J'ai donc pu résoudre mon problème en optant pour les await et async, c'est à priori plus optimisé pour ce que je fais.

    Bonne journée.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Disque dur externe qui provoque des freeze
    Par mitsukk1992 dans le forum Dépannage et Assistance
    Réponses: 16
    Dernier message: 28/04/2017, 10h20
  2. GUI font units to 'normalized' freezes Matlab
    Par Jessica_S dans le forum Interfaces Graphiques
    Réponses: 0
    Dernier message: 09/05/2012, 00h34
  3. [C#] BackgroundWorker + changement GUI
    Par Carl2010 dans le forum Windows Presentation Foundation
    Réponses: 5
    Dernier message: 07/03/2011, 00h30
  4. Thread, freeze GUI et priorités
    Par Niak74 dans le forum Qt
    Réponses: 9
    Dernier message: 24/09/2009, 11h34
  5. Freeze du gui
    Par daviddu54 dans le forum Windows Forms
    Réponses: 2
    Dernier message: 18/11/2007, 21h56

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