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

Développement Windows Discussion :

Application WinForm qui reste en processus en arrière plan après fermeture


Sujet :

Développement Windows

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2017
    Messages
    42
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Gard (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mars 2017
    Messages : 42
    Par défaut Application WinForm qui reste en processus en arrière plan après fermeture
    Salut à tous.

    Me revoilà avec mon application Winform

    Aujourd'hui j'ai constaté que quand l'application se ferme, elle se met en processus d'arrière plan.

    Voici le cheminement de connexion à la BDD lors de l'ouverture et la fermeture de l'application :

    Connexion BDD
    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
    49
    50
    51
    52
    53
    54
     
    /// <summary>
            /// Arrête la connexion à la fermeture du dashbord
            /// </summary>
            public static void KillConnexion()
            {
                zCnx.Close();
                zCnx.Dispose();
                zCnx = null;
            }
     
    public static Boolean bOpenConnection()
            {
                try
                {
                    if(zCnx == null)
                    {
                        // Test : Mode débug actif ?
                        //bool bDebug = System.Diagnostics.Debugger.IsAttached;
     
                        bool bDebug = false;
     
    #if DEBUG
                        bDebug = true;
    #endif
     
                        // Test : En mode débug ?
                        if (bDebug)
                        {
                            zCnx = new MySqlConnection("Persist Security Info=False;Server="";Database="";Uid="";Pwd=""");
                            zCnx.Open();
                        }
                        else
                        {
                            // Mise à jour du serveur
                            MajServeurs();
                            // prod
                            zCnx = new MySqlConnection("Persist Security Info=False;Server=" + zsServeurMySql + ";Database="";Uid="";Pwd=""");
                            zCnx.Open();
                        }
     
     
                    } // Test : En mode débug ?
     
     
                    return true;
                }
                catch (Exception e)
                {
                    cl_Logger.writeError(WindowsIdentity.GetCurrent().Name, "cl_DbConnection", "openConnection", e);
                    // Erreur
                    return false;
                }
            }
    Chargement
    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
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
     
    public partial class frm_Chargement : Form
        {
            public frm_Chargement()
            {
                InitializeComponent();
                this.Text = "Chargement de l'application - " + cl_Configuration.numeroVersion;
            }
     
            /// <summary>
            /// Lorsque la fenêtre s'affiche.
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void frm_Chargement_Shown(object sender, EventArgs e)
            {
                //La fenêtre de chargement ne charge que les données nécessaire à l'ouverture de l'application,
                //le reste des données est chargé de façon asynchrone
                Thread t = new Thread(Initialisation);
                t.Start();
                while (t.IsAlive)
                {
                    Application.DoEvents();
                }
                this.Close();
            }
     
            /// <summary>
            /// Méthode permettant de récupérer les informations nécessaire à l'ouverture de l'application
            /// </summary>
            public void Initialisation()
            {
     
                //Récupérer l'indentifiant session
                String strNameWindows = WindowsIdentity.GetCurrent().Name;
     
                //Récupérer le nombre de caractère qu'il y a jusqu'au nom de l'utilisateur
                int nCaractere = strNameWindows.IndexOf(@"\");
     
                // Test : Ouverture de la connexion à la base de données
                if (!cl_DbConnection.bOpenConnection()) 
                {
                    MessageBox.Show("Connexion au serveur impossible");
                    // Fermeture de l'application
                    Environment.Exit(0);
                } // Test : Ouverture de la connexion à la base de données
     
                //Déclaration des objets
                cl_CollaborateurBUS userbus = new cl_CollaborateurBUS();
                cl_PrevisionBUS PreviBUS = new cl_PrevisionBUS();
     
                //Variable qui va contenir le login
                string strName = null;
     
                //exemple : kevinpl
                strName = strNameWindows.Substring(nCaractere + 1, strNameWindows.Length - nCaractere - 1);
                //strName = "alaeddine";
                // Test : Mode débug actif ?
                if (System.Diagnostics.Debugger.IsAttached)
                {
                    //strName = "pierrot";
                    //strName = "Aurelienb";
                    //strName = "fabienne";
                    //strName = "alaeddine";
                } // Test : Mode débug actif ?
     
                // si le nom de domaine est ISILAND
                if (strNameWindows.Substring(0, nCaractere) == "ISILAND" )
                {
                    //si il n'y a pas d'utilisateur dont le login commence par le nom passé en parametre 
                    if (userbus.checkDoublon(strName))
                    {
                        //Récupérer l'objet utilisateur
                        cl_Collaborateur.CurrentUser = userbus.getUserByLogin(strName);
                    }
                }
                else
                {
                    MessageBox.Show("Le nom de domaine est incorrecte");
                    // Fermeture de l'application
                    Environment.Exit(0);
                }
            }
     
            /// <summary>
            /// Récupère les informations supplémentaires (par ex. les droits de l'utilisateur) à partir des webservices
            /// </summary>
            public void Rafraichir()
            {
                cl_CollaborateurBUS UserBUS = new cl_CollaborateurBUS();
                cl_PrevisionBUS PreviBUS = new cl_PrevisionBUS();
     
                List<cl_Collaborateur> liste = new List<cl_Collaborateur>();
     
                //Vérifie si l'utilisateur est RPA ou non
                cl_Collaborateur.RPA = UserBUS.checkRPA(UserBUS.getUserCodeCollab(cl_Collaborateur.CurrentUser.idcollaborateur).CodeCollab);
     
                //Récupère la liste des collaborateurs que l'utilisateur courant peut affecter
                string[] strListe = UserBUS.getListeCollaborateurAutorise(UserBUS.getUserCodeCollab(cl_Collaborateur.CurrentUser.idcollaborateur).CodeCollab);
     
                //Insère dans une liste static des objets utilisateur pouvant être affecter par l'utilisateur courant
                foreach (string strCodeCollab in strListe)
                {
                    liste.Add(UserBUS.getUserAutorise(strCodeCollab));
                }
     
                //liste des utilisateurs dont l'utilisateur courant à la responsabilité
                cl_Collaborateur.LstUsers = liste;
     
                //Actualiser la liste des jours non-travaillés de la période allant de trois mois auparavavant jusqu' a trois mois après la date actuelle
                PreviBUS.setListJourNonTravaille();
     
                //Actualiser la liste des prévisions de temps partiel de la période allant de trois mois auparavant jusqu'a trois mois apres la date actuelle.
                PreviBUS.setLstPrevisionTpsPartiel();
     
            }
    Lors de la fermeture de l'application
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
                cl_DbConnection.KillConnexion();
            }
    Et la classe program
    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
     
    static class Program
        {
            /// <summary>
            /// Point d'entrée principal de l'application.
            /// </summary>
            [STAThread]
     
            static void Main()
            {
                // French (fr)
                System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("fr-FR");
                System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("fr-FR");
     
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
     
                frm_Chargement frmCharg = new frm_Chargement();
                //L'affichage de la fenêtre de chargement entraîne le chargement des données
                frmCharg.Show();
     
                // Chargement des droits du collaborateur
                Application.DoEvents();
                frmCharg.Rafraichir();
     
                //Si l'utilisateur est RPA, ouverture d'une fenêtre responsable
                if (cl_Collaborateur.RPA)
                {
                    frm_Responsable frmResponsable = new frm_Responsable();
                    frmResponsable.Show();
                    //Application.Run(new frm_Responsable());
                }
                //Sinon, actualisation du plan de charge de la fenêtre collaborateur
                else
                {
                    frm_Collaborateur frmCollab = new frm_Collaborateur();
                    //Affichage d'une fenêtre collaborateur
                    frmCollab.Show();
                }
     
                //Permet à l'application de continuer de tourner à la fin des traitements
                Application.Run();
     
            }
        }
    Je n'ai absolument aucune idée de pourquoi ça peut faire ça !
    Merci d'avance

  2. #2
    Expert confirmé
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    Février 2010
    Messages
    4 197
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2010
    Messages : 4 197
    Billets dans le blog
    1
    Par défaut
    Un programme qui reste chargé en sortie est généralement un programme qui ne libère pas convenablement ses ressources.

    Ceci est souvent dû à des instances d'objets IDisposable (faisant appel à des ressources non managées) qui n'ont pas été correctement disposé.


    Ouvrir une connexion persistante de bout en bout de l'exécution du programme est une très mauvaise pratique.
    En effet, la connexion est un objet IDisposable, non managé.
    Cela veut dire que si ton programme sort d'une manière non prévue, alors la connexion ne sera pas libérée.

    Mais cela veut dire aussi que si tu as une coupure réseau, même une micro-coupure, alors la connexion tombe dans un état second et ton programme va à coup sûr mal le gérer et planter... accessoirement en laissant la ressource ouverte.

    Cela veut dire aussi que si tu écrits une transaction et que tu oublies de la terminer, elle va rester ouverte jusqu'à la fermeture du programme. Les conséquence peuvent être désastreuses : faire un rollback en fin de semaine d'une transaction initiée le lundi matin, c'est vraiment pas cool... et locker des ressources de plus en plus nombreuses a fil de la journée, c'est pas cool non plus pour les autres !

    Bref : la connexion, on l'ouvre le plus part possible, et on la ferme le plus tôt possible.

    Ouvrir et fermer une connexion pour chaque requête est une meilleure pratique que d'ouvrir une connexion le lundi matin et la fermer le vendredi soir.

    Dans l'idéal, on ouvre la connexion au début de chaque traitement, et on la ferme à la fin de chaque traitement.

    Pourquoi ?
    - Lorsqu'une personne utilise l'application, les temps de traitements dans la base de données sont de l'ordre de moins de 1% du temps de saisie. Ceci implique que laisser une connexion ouverte consomme des ressources (et une licence si on est en mode connexions concurrentes) 99% du temps pour rien.
    - Tous les SGBD dignes de ce nom, y compris MySQL, c'est pour dire, possèdent un "pool de connexion". C'est à dire qu'en réalité le SGBD ne va pas réellement ouvrir et fermer une connexion à chaque accès : il va simplement réutiliser une connexion déjà ouverte, libre, puis la libérer : ceci permet d'avoir 1000 personnes connectées en simultané qui ne consomment que 10 connexions parallèles.
    - Si tu travailles avec un objet connexion instancié dans un bloc "using", alors quelle que soit la manière dont tu sors du bloc, la connexion sera forcément fermée et libérée.
    - Si la connexion ne reste jamais ouverte plus de 2 secondes d'affilées au lieu de 5 jours, les risque de micro-coupure est quelque peu diminué, améliorant sensiblement la stabilité de l'application.

    Sinon, pour essayer de résoudre ton problème sans tout réécrire, tu peux tenter de rajouter dans tous les Form_Closed de ton application un GC.Collect().
    Mais sans fermer proprement la connexion avant, il y a peu de chances que ça change grand chose (même si au pire ça peut pas faire de mal).

    Ah, et j'oubliais... afin d'éviter de s'emmêler les pinceaux avec MARS, des transactions croisées et autres blocages, il ne faut jamais utiliser la même connexion dans deux threads différents : chaque thread doit disposer de sa propre connexion (ce qui est automatiquement le cas si tu ouvres et fermes ta connexion à chaque traitement).

  3. #3
    Membre actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2017
    Messages
    42
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Gard (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mars 2017
    Messages : 42
    Par défaut
    Merci pour cette explication très complète.

    j'ai modifier pour ouvrir et fermer les connexions juste quand on en avait besoin.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    // Mise à jour du serveur
    MajServeurs();
    // prod
    using (zCnx = new MySqlConnection("Persist Security Info=False;Server=" + zsServeurMySql + ";Database="";Uid="";Pwd=''"))
    {
       zCnx.Open();
       // code ici
       KillConnexion();
    }
    j'ai fais ça pour chaque appel à la base de données.

    Malheureusement, cela n'a pas réglé mon soucis. L'application, une fois fermée reste dans le processus d'arrière plan où seul un peu de mémoire est utilisée.

    Pour le thread, je ne l'instancie qu'une fois au démarrage de l'application :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    private void frm_Chargement_Shown(object sender, EventArgs e)
            {
                //La fenêtre de chargement ne charge que les données nécessaire à l'ouverture de l'application,
                //le reste des données est chargé de façon asynchrone
                Thread t = new Thread(Initialisation);
                t.Start();
                while (t.IsAlive)
                {
                    Application.DoEvents();
                }
                this.Close();
            }
    Est-ce que j'oublie quelque chose ?

  4. #4
    Expert éminent Avatar de Pol63
    Homme Profil pro
    .NET / SQL SERVER
    Inscrit en
    Avril 2007
    Messages
    14 204
    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 204
    Par défaut
    Citation Envoyé par StringBuilder Voir le message
    Un programme qui reste chargé en sortie est généralement un programme qui ne libère pas convenablement ses ressources.
    Ceci est souvent dû à des instances d'objets IDisposable (faisant appel à des ressources non managées) qui n'ont pas été correctement disposé.
    non

    Citation Envoyé par StringBuilder Voir le message
    la connexion est un objet IDisposable, non managé.
    Cela veut dire que si ton programme sort d'une manière non prévue, alors la connexion ne sera pas libérée.
    non plus

    Citation Envoyé par StringBuilder Voir le message
    Mais cela veut dire aussi que si tu as une coupure réseau, même une micro-coupure, alors la connexion tombe dans un état second et ton programme va à coup sûr mal le gérer et planter... accessoirement en laissant la ressource ouverte.
    ca peut se gérer
    management studio quand on ouvre une fenetre de requete c'est une connection et elle peut rester ouverte des semaines sans problèmes
    même si c'est rarement recommandable avoir une connection pour le temps de l'appli c'est pas forcément dérangeant (si c'est bien fait)

    Citation Envoyé par StringBuilder Voir le message
    Dans l'idéal, on ouvre la connexion au début de chaque traitement, et on la ferme à la fin de chaque traitement.
    oui

    pour info quand tu fermes une connection en .net elle n'est pas fermée
    tout comme le sgbdr fait un pool, .net en fait un aussi et dès que tu demandes une connexion tu en récupères une que .net a gardé



    Citation Envoyé par StringBuilder Voir le message
    Sinon, pour essayer de résoudre ton problème sans tout réécrire, tu peux tenter de rajouter dans tous les Form_Closed de ton application un GC.Collect().
    Mais sans fermer proprement la connexion avant, il y a peu de chances que ça change grand chose (même si au pire ça peut pas faire de mal).
    toujours pas
    une appli se ferme quand tous ses threads sont arrêtés, et quand tous ses threads sont arrêtés elle libère sa mémoire sans problème.
    IDisposable permet normalement d'oublier de disposer vu que le destructeur appelle Dispose



    @virtazp
    il faut ajouter t.IsBackground = true avant de faire t.Start()
    ce qui signifie que ce thread s'arrêtera tout seul quand le thread principal s'arrêtera
    et application.doevents c'est à éviter
    Cours complets, tutos et autres FAQ ici : C# - VB.NET

  5. #5
    Membre actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2017
    Messages
    42
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Gard (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mars 2017
    Messages : 42
    Par défaut
    Merci pour vos réponses.

    Ca ne fonctionne pas non plus avec

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    Thread t = new Thread(Initialisation)
    {
       IsBackground = true,
    };
    Je viens de voir qu'en mode Debug, lorsque je ferme la fenêtre, il faut en plus que je clique sur le bouton stop pour vraiment stopper l'application. Je ne sais pas si cela est lié !

    Si je débug mon programme en cours d’exécution (celui qui reste dans les processus arrière plan), j'ai ce message qui apparaît toutes les minutes environ :

    Le thread 0x1080 s'est arrêté avec le code 0 (0x0).
    Le thread 0x3c38 s'est arrêté avec le code 0 (0x0).
    Le thread 0x1074 s'est arrêté avec le code 0 (0x0).
    Le thread 0x47a4 s'est arrêté avec le code 0 (0x0).
    Le thread 0x4b4c s'est arrêté avec le code 0 (0x0).
    Le thread 0xc14 s'est arrêté avec le code 0 (0x0).
    Le thread 0x217c s'est arrêté avec le code 0 (0x0).
    Le thread 0x4b30 s'est arrêté avec le code 0 (0x0).
    Le thread 0x4608 s'est arrêté avec le code 0 (0x0).
    etc...

  6. #6
    Expert éminent Avatar de Pol63
    Homme Profil pro
    .NET / SQL SERVER
    Inscrit en
    Avril 2007
    Messages
    14 204
    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 204
    Par défaut
    Le bouton stop arrête tous les threads, il doit t'en rester un en cours qui n'est pas isbackground
    Cours complets, tutos et autres FAQ ici : C# - VB.NET

  7. #7
    Membre actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2017
    Messages
    42
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Gard (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mars 2017
    Messages : 42
    Par défaut
    Nom : Capture.PNG
Affichages : 860
Taille : 12,8 Ko

    En regardant, il s'agit d'un travail d'impression ??!!
    Je comprend plus rien.

  8. #8
    Expert confirmé
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    Février 2010
    Messages
    4 197
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2010
    Messages : 4 197
    Billets dans le blog
    1
    Par défaut
    Effectivement, j'avais oublié le coup du Thread fou qui ne s'arrête pas.

    Par contre, Pol63 je veux bien ta source pour le pool géré côté .NET

    Je ne vois pas comment ni pourquoi ce serait le cas. C'est au SGBD de gérer ça : si .NET crée lui-même un pool local, ça veut dire que des connexions sont ouvertes et restent réservées inutilement côté serveur. Ça n'a pas de sens.


    Pour en revenir au thread fou, dans ton événement FormClosing, il faut que tu fasses un Abort() sur chaque thread.

    Ou plus propre, tu implémentes une méthode "StopPlease()" à tes threads, qui passe à true une variable "shouldStop".
    Tu testes régulièrement cette variable dans la boucle du thread et tu arrêtes ton trairement proprement quand elle passe à true.

Discussions similaires

  1. [XL-2010] Propriété qui met le chart à l'arrière plan?
    Par Gualino dans le forum Macros et VBA Excel
    Réponses: 6
    Dernier message: 06/10/2011, 19h29
  2. Lancer un processus en arrière-plan
    Par Morgan7469 dans le forum C#
    Réponses: 10
    Dernier message: 17/03/2011, 15h48
  3. Changer la priorité des processus d'arrière plan Oracle
    Par dcollart dans le forum Administration
    Réponses: 1
    Dernier message: 16/07/2010, 16h22
  4. Lancer sous la console dos un processus en arrière plan.
    Par ziad.shady dans le forum Scripts/Batch
    Réponses: 6
    Dernier message: 25/10/2009, 15h44
  5. Lancer des processus en arrière plan
    Par momeftah dans le forum Shell et commandes GNU
    Réponses: 11
    Dernier message: 01/05/2007, 19h50

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