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

Windows Forms Discussion :

[Threading] Rendre une image de chargement (in)visible - Casse-tête


Sujet :

Windows Forms

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre actif
    Homme Profil pro
    Inscrit en
    Mai 2012
    Messages
    22
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2012
    Messages : 22
    Par défaut [Threading] Rendre une image de chargement (in)visible - Casse-tête
    Bonjour bonjour!

    Je me suis pris la tête là-dessus toute la journée, j'admets n'avoir jamais trop touché au multithreading, mais quand même je suis sur les fesses...

    Mise en situation:
    J'ai donc une Form qui effectue des opérations "lourdes" (connexion, chargement de données, etc). Je fais faire ces opérations par un thread créé sur le tas, et j'affiche un petit gif animé pendant ce temps-là. Sauf que j'ai à un moment donné deux appels d'affilée à ma fonction "fais une opération en fond", et comme je rend le gif (in)visible de façon thread safe asynchrone, il semble que ça ne suive pas et que mon gif reste invisible...

    Mais comme des sources parlent mieux que des mots:

    -Les fonctions générales
    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
     
    // Permet d'effectuer une modification thread-safe sur un contrôle
    protected void addControlWork(Control c, Action a)
    {
    	if (c.InvokeRequired)
    		c.BeginInvoke(new MethodInvoker(a));
    	else
    		a();
    }
     
    //Lance une tâche de fond, affiche le logo chargement, désactive les autres contrôles
    protected void doWorkInBackground(MethodInvoker )
    {
    	addControlWork(loadingPicture, delegate() { loadingPicture.Visible = true; });
    	addControlWork(mainPanel, delegate() { mainPanel.Enabled = false; });
    	f.BeginInvoke(new AsyncCallback(endWorkInBackground), null);
    }
     
    //A la fin d'une tâche de fond, efface le logo chargement, active les autres contrôles
    protected void endWorkInBackground(IAsyncResult result)
    {
    	if (this.Disposing)
    		return;
    	addControlWork(loadingPicture, delegate() { loadingPicture.Visible = false; });
    	addControlWork(mainPanel, delegate() { mainPanel.Enabled = true; });
    }
    -L'utilisation:

    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
     
    private void uneListBox_SelectedIndexChanged(object sender, EventArgs e)
    {
    	doWorkInBackground((MethodInvoker)(() =>
    	{
    		object[] source = LongueOperation();;
    		addControlWork(uneComboBox, delegate()
    		{
    			// Changing DataSource fires comboBox.SelectedIndexChanged
    			uneComboBox.DataSource = source;
    		});
    	}));
    }
     
    private void uneComboBox_SelectedIndexChanged(object s, EventArgs e)
    {
     
    doWorkInBackground((MethodInvoker)(() =>
    {
    // Le code est pas intéressant ici
    	object[] range = uneOperationLourde();
    	object[] source = uneAutreOperationLourde();
    	addControlWork(uneListBox, delegate() { uneListBox.Items.AddRange(range); });
    	addControlWork(uneComboBox, delegate() { uneComboBox.DataSource = source; });
    }));
     
    }
    Dans l'exemple ci-dessus, on peut voir que lorsque uneListBox_SelectedIndexChanged est appelé, uneComboBox_SelectedIndexChanged est appelé immédiatement après. Et c'est là que le bât blesse, mon pov' piti logo reste invisible, seul et abandonné dans les méandres de la mémoire de ma machine.

    Je souhaite donc savoir si quelqu'un a une idée de ce qui cloche, de comment améliorer ça, ou tout simplement si quelqu'un peut me donner une meilleure marche à suivre (j'ai entendu parler des await en 4.5?)

    Merci bien!

    Edit: Evidemment j'ai des solutions pansements, du genre créer deux fonctions startLoading et stopLoading dans lesquelles je m'occupe de la visibilité du logo et de l'activation du panel, mais c'est pas forcément très intuitif.

    Edit: Pour résumer, ma fonction de démarrage de tâche de fond qui rend visible le gif de chargement est à un moment appelée avant qu'une autre tâche de fond ne soit terminée. Donc cette dernière se termine juste après le démarrage de la suivante, rendant mon gif invisible pendant le traitement de celle-ci. Il y a un moyen de "queue" les tâches de fond?

  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 : 37
    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
    Salut,
    que penses-tu de ç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
     
    private void uneListBox_SelectedIndexChanged(object sender, EventArgs e)
            {
                pictureBox1.Visible = true;
     
                Task.Factory.StartNew(() =>
                    {
                        Thread.Sleep(5000);
                        //combobox.Datasource = .... chargement des données //peut-être qu'il faut un invoke sur la combobox comme ci-dessous mais je ne crois pas
     
                        pictureBox1.Invoke(new MethodInvoker(delegate //on cache la picture box lorsque le travail est terminé
                        {
                            pictureBox1.Visible = false;
                        }));
     
                    });
            }
    Je suis pas spécialiste en multi th mais je crois que c'est tes BeginInvoke qui !@#
    J'ai pas cru voir de EndInvoke donc tu ne forces jamais le programme à fini ce qu'il droit faire dans le BeginInvoke(). Ça revient à dire : tient faudra faire ça quand t'as le temps. Mais si le thread principal se fini avant hein t'embête pas on oubli ^^
    En gros.. mais je suis pas spécialiste et s'il faut rien à voir ^^

    A+

  3. #3
    Membre Expert Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Par défaut
    Bonjour/bonsoir.

    Je vois le problème :
    a) A la fin de ta première opération asynchrone (démarrée dans uneListBox_SelectedIndexChanged) tu as placé en file d'attente "uneComboBox.DataSource = ..." suivi de "loadingPicture.Visible = false".
    b) Le premier élément est appelé, qui appelle à son tour "uneComboBox_SelectedIndexChanged" qui effectue immédiatement "loadingPicture.Visible = true" plutôt que de le mettre en file d'attente vu qu'on est sur le thread UI. Mais puisque la file d'attente contient toujours "loadingPicture.Visible = false", ce dernier appel l'emporte.


    Voici une mise en oeuvre simple et correcte:
    Code c# : 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
    async void uneListBox_SelectedIndexChanged(object sender, EventArgs e)
    {
        // Thread UI
        SuspendUI();
     
        // Arrière-plan
        object[] source = await Task.Run(LongueOperation);
     
        // Thread UI
        uneComboBox.DataSource = source;
        ResumeUI();
    }
     
    async void uneComboBox_SelectedIndexChanged(object sender, EventArgs e)
    {
        // Thread UI
        SuspendUI();
     
        // Arrière-plan (pour plus d''efficacité on pourrait les lancer en parallèle et attendre avec Task.WaitAll)
        object[] range = await Task.Run(uneOperationLourde);
        object[] source = await Task.Run(uneAutreOperationLourde);
     
        // Thread UI
        uneListBox.Items.AddRange(range);
        uneComboBox.DataSource = source;
        ResumeUI();
    }
     
    int suspensionCounter;
    void SuspendUI()
    {
        ++suspensionCounter;
        if (suspensionCounter != 1) return;
        loadingPicture.Visible = true;
        mainPanel.Enabled = false;
    }
    void ResumeUI()
    {
        --suspensionCounter;
        if (suspensionCounter != 0) return;
        loadingPicture.Visible = false;
        mainPanel.Enabled = true;
    }

  4. #4
    Membre actif
    Homme Profil pro
    Inscrit en
    Mai 2012
    Messages
    22
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2012
    Messages : 22
    Par défaut
    Merci beaucoup pour les réponses j'ai eu le temps de me pencher sur le sujet, et malheureusement je n'ai pas accès à async/await dans mon environnement de travail!

    Donc pour l'instant, j'ai juste remplacé les DataSource = truc par une fonction qui neutralise les events selectedIndexChanged, donc une opération à la fois. Ca marche mais c'est pas idéal quoi

    J'ai pas cru voir de EndInvoke donc tu ne forces jamais le programme à fini ce qu'il droit faire dans le BeginInvoke()
    En fait, c'est dans cette ligne:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    f.BeginInvoke(new AsyncCallback(endWorkInBackground), null);
    BeginInvoke qui demande une fonction callback quoi, appelée j'imagine dès que la fonction f est terminée, mais p'têt que j'me trompe

  5. #5
    Membre Expert Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Par défaut
    Citation Envoyé par Kilazur Voir le message
    Merci beaucoup pour les réponses j'ai eu le temps de me pencher sur le sujet, et malheureusement je n'ai pas accès à async/await dans mon environnement de travail!
    Async/await aurait permis de simplifier le code mais la clé de la solution que j'avais proposée c'était SuspendUI/ResumeUI avec l'utilisation d'un compteur de suspension. Tu pourrais très bien utiliser cette solution avec ton code d'origine.

    BeginInvoke qui demande une fonction callback quoi, appelée j'imagine dès que la fonction f est terminée, mais p'têt que j'me trompe
    C'est tout à fait ça.

  6. #6
    Membre actif
    Homme Profil pro
    Inscrit en
    Mai 2012
    Messages
    22
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2012
    Messages : 22
    Par défaut
    Citation Envoyé par DonQuiche Voir le message
    Async/await aurait permis de simplifier le code mais la clé de la solution que j'avais proposée c'était SuspendUI/ResumeUI avec l'utilisation d'un compteur de suspension. Tu pourrais très bien utiliser cette solution avec ton code d'origine.
    J'ai tenté d'implémenter un truc du genre en vain, et c'était vraiment... sale
    A base de compteur de nombres de tâches en fond et de création de thread qui contiennent juste une boucle while avant l'appel à la création d'une nouvelle tâche de fond... Bref, une horreur sans nom Et pis ça marchait pas

    Et SuspendUI, ça ne va pas... ben, suspendre l'UI et mon gif qui va avec?

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

Discussions similaires

  1. récupérer la taille totale d'une image avant chargement
    Par sorenson dans le forum Général JavaScript
    Réponses: 2
    Dernier message: 25/06/2006, 15h50
  2. rendre une image flottante
    Par pierrot10 dans le forum Général JavaScript
    Réponses: 5
    Dernier message: 14/06/2006, 10h04
  3. [FLASH 8] Redimensionner une image au chargement
    Par robocop2776 dans le forum Flash
    Réponses: 2
    Dernier message: 30/01/2006, 15h18
  4. rendre une image transparente
    Par nabil dans le forum VB 6 et antérieur
    Réponses: 16
    Dernier message: 12/06/2005, 14h53
  5. rendre une image transparente
    Par matt92700 dans le forum AWT/Swing
    Réponses: 6
    Dernier message: 02/06/2005, 09h42

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