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 Presentation Foundation Discussion :

[WPF][MultiThreading/Composants] Petit probleme de conception ?


Sujet :

Windows Presentation Foundation

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Décembre 2006
    Messages
    45
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 45
    Par défaut [WPF][MultiThreading/Composants] Petit probleme de conception ?
    Bonjour a tous,

    Cela fait quelques semaines que je me suis décide a créer une petite application en WPF, et aujourd'hui c'est chose entamée !

    Au fur et a mesure de mon développement, je me rends compte que le multithread m'est indispensable (que je n'ai pas beaucoup utilise non plus).

    Le contexte général :

    Mon programme est en fait un mini gestionnaire de téléchargements.


    Ma conception :

    Pour le multithread, j'utilise le ThreadPool de la classe du même nom afin de gérer la distribution de taches a mes threads. Je n'utilise que le minimum de thread possibles (2 pour un dual core donc) au départ mais je suis censé l'augmenter comme bon me semble.

    Je lance donc une queue de 400 taches que mon ThreadPool exécute 2 par 2.

    Chaque thread = 1 téléchargement d'un fichier sachant qu'un fichier peut contenir plusieurs liens a télécharger.

    J'ai mon formulaire MainWindow, qui contient un StackPanel vide au départ.

    Une fois que j'appuies sur un bouton, les 400 taches se mettent en attente et les 2 téléchargements s'effectuent. (Via la classe WebClient)

    Le StackPanel est cense contenir un UserControl (que je vais appeler ToxiBar) qui contient quelques labels, boutons et une progressBar.

    Vous l'aurez surement compris, ce StackPanel est censé afficher l'état d'avancement du téléchargement en cours et que donc, il devrait y avoir 2 ToxiBars a chaque fois..


    Mon probleme :

    La tache, que j'envoisa mon ThreadPool, est la méthode d'un nouvel Objet. Cette objet est donc sur un thread a part, en dehors de celui de mon MainWindow.

    Comme vous le savez, il n'est pas possible (de manière propre) de modifier le formulaire via un autre Thread que celui qui l'a créé.

    Ce que je voudrais donc faire, c'est qu'a chaque fois qu'un nouveau Thread est lance, un nouveau composant ToxiBar s'ajoute a mon StackPanel, et qu'il affiche la progression du téléchargement de ce Thread.


    Mes questions :

    - Vu que j'utilise le ThreadPool, je n'ai aucun controle sur ces threads, comment un thread peut envoyer au thread principal son id ?

    - Voyez-vous une meilleure manière/conception ?!


    Mes reflexions :

    - Je voulais créer une Hashtable dans mon MainWindow, y ajouter comme cle, l'id unique de mon thread, et comme valeur, l'objet ToxiBar qui lui est associe.. Cependant, via mon thread je ne peux accéder aux membres de mon MainWindow, sauf si je le déclare en static mais je trouve ca moche..

    D'autant plus l'accès a la Hastable n'est pas thread-safe, si on ne la synchronise pas (fonction Synchronized me semble d'après le MSDN).

    - Une methode static sur mon formulaire ? Mais elle n'aura pas accès aux membres non static comme ma hashtable, et encore une fois je trouve que ca fait moche si je la rends static non ?
    Pour les controles par contre je suppose que ca se fait par les Invoke, je l'ai déjà fait ce ne pose pas de problème.

    - J'ai aussi songe a l'utilisation de la classe ManagedThreadPool de l'ami Stephen Toub de chez Microsoft disponible ici : http://www.koders.com/csharp/fidD26A...3Asemaphore.cs

    Elle m'a l'air intéressante et avec celle-ci j'aurai des infos sur tous mes threads depuis mon formulaire et pourrai donc associer un thread a un ToxiBar..
    Mais selon Ericv (de Microsoft aussi) un Thread est très couteux, et qu'il fallait privilégier les Tasks (voir son article : partie 1 et partie 2
    Or, cette classe n'utilise que les Threads..
    Me conseillez-vous d'utiliser cette classe, mais en remplaçant les Threads par des Tasks ?

    Ayant 400 taches a donner, et les exécutant 2 par 2 (sachant que je peux augmenter ce nombre comme bon me semble ce n'est pas une valeur en dur, fixe, mais bien dynamique).. ce la peut se mettre a consommer un peu trop de mémoire CPU..

    Voili voila.. Je crois avoir tout dit.. J'ai écris ce message en plusieurs fois donc s'il manque des précisions, n'hésitez pas je vous répondrai

    Merci !

  2. #2
    Membre éclairé
    Inscrit en
    Octobre 2005
    Messages
    62
    Détails du profil
    Informations forums :
    Inscription : Octobre 2005
    Messages : 62
    Par défaut
    Heu ?

    Tu as ta liste de 400 tâches. Des objets "Tache" ? Tu peux donc afficher la liste de ces tâches, en filtrant pour ne prendre que celles "en cours".
    Tu peux lancer, dans ton autre thread, la méthode "télécharger" de ta "Tache", qui s'occupera de mettre à jour l'avancement de la tâche (ce qui mettra à jour l'UI automatiquement, si tu as bien implémenté INotifyPropertyChanged).

    Après, comment tu affiches la tâche, avec un UC ou autres, ça ne pose pas de problème.

    Je ne pense pas avoir été très clair; c'est ce que j'utilise dans mon cas, donc c'est clair dans ma tête '^^

  3. #3
    Membre Expert
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    1 103
    Détails du profil
    Informations personnelles :
    Âge : 47
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 1 103
    Par défaut
    Les threads sont effectivement couteux en termes de ressources, et heureusement pour toi, la threadpool en alloue quelque uns quand elle démarre avec ton appli .NET.

    A partir de là tu peux coller à ces threads de travail des tâches ponctuelles à faire.
    je parle de la vraie ThreadPool du système hein pas une que t'aurais trouvé ailleurs.

    il n'est pas impossible de faire ce que tu veux proprement, d'avertir depuis un thread un changement d'état pour que l'ui en prenne conscience...

    tous les composants de wpf au même titre que les winforms disposes de méthodes de synchronisation de contexte, pour pallier aux problèmes de cross-threading.

    moi je te conseil un modèle comme suite, qui respecte le modèle MVVM.
    dans ton ViewModel,

    tu défini la méthode qui va faire le download, et qui est une "Task" que tu va donc mettre en queue dans la pool, et ce que tu va faire, c'est que tu va mettre les 400 en queue dans la pool direct, et tu t'occupe pas de savoir combien elle en exécute en même temps bien que tu puisse changer ce paramètre.

    Le type Task est un descripteur de tâche en cours.
    Lorsque tu empile les job dans la ThreadPool tu les empile en fournissant un objet de départ avec lequel la méthode sera appelée... parfait tu lui fourni donc la tâche dont elle à besoin.
    Lorsqu'une tâche est démarrée dans la thread-pool tu lui fait dépiler un élément de la pile (sur laquelle tu a pris soin de mettre les 400 entités descriptives)
    ensuite cette tâche lance le téléchargement et ajoute à une ObservableCollection<Task> (de façon synchrone) le descripteur de tâche en cours.
    ensuite cette même tache ce contente de modifier les propriétés de son instance de descripteur Task.

    je vais donc te montrer un peu de code :
    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
     
    public class Task : INotifyPropertyChanged
    {
         private string _url;
         public string string Url {get { return _url; } set { _url = value; doNotify("Url"); }
         public int Percent { get { return _percent; } set { _percent = value; doNotify("Percent"); }
     
         public event PropertyChangedEventHandler PropertyChanged;
         private void doNotify(string name) {
              if (PropertyChanged != null)
              {
                   PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
              }
         }
    }
     
    public class DownloadViewModel : ViewModelBase {
    private object locker;
    public readonly ObservableCollection<Task> Tasks;
     
    ...
    public DownloadViewModel() {
        locker = new object();
         Tasks = new ObservableCollection<Task>();
     
         // ici tu instancie les 400 objets Task
         List<Task> temp = new List<Task>();
         ...
     
         // on démarre les 400 tâches
         foreach (var t in temp)
              System.Threading.ThreadPool.QueueUserWorkItem(downloadTask, t);
    }
     
    private void downloadTask(Object state)
    {
         Task myTask = state as Task;
         lock (locker) Tasks.Add(myTask);
     
         // ici mon traitement... à chaque fois que je reçoit un changement de pourcentage je modifie Percent de myTask.
         // tu n'est pas obligé de synchroniser l'accès à Percent de myTask... on peut le considérer comme volatile.
    }
     
    }
    Toute l'astuce consiste à appliquer ton ViewModel à ta windows, et ta list tu la bind à Tasks.
    Et tu modifie le ItemTemplate pour qu'il utilise tes usercontrol ou tu les définis àla mano avec du databinding...

    Tu notera que j'ai défini ma classe comme héritant de ViewModelBase, il s'agit d'une classe qui implante l'interface INotifyPropertyChanged.
    Ici ce n'est pas très important car finallement la seule propriété réellement exportée est Tasks qui est de type ObservableCollection, donc une collection qui dispose de son propre mécanisme de Notify.

    Note : Inutile de te poser la question pour le nombre de tâches en cours... la threadpool a un nombre de threads instanciés au démarrage, par défaut, dès qu'un se libère elle lui attribue la première tache en queue...
    Dans ton cas, limiter à 2 sous prétexte que la machine est un dual core n'est pas nécessaire et n'a aucun sens, car tu ne peux en aucun cas prévoir comment les threads vont être placés sur les core et ils peuvent très bien se retrouver régulièrement sur le même, car de plus ils vont changer pendant toutes leur durées de vie, y compris au sein même d'une seule tâche...
    De plus tu oublie qu'en réalité ton appli à 3 thread au final... 1 main thread qui est le thread UI et 2 threads de pool qui tourne en permanence et les autres qui eux tournes mais n'exécute rien temps qu'on a pas une tite tache à leur confier.
    D'ailleurs tu ne le sais même pas mais WebClient utilise un thread également pour les téléchargement... au final le nombre de threads de ton application va dépasser de loin le nombre de core, mais ce détail ne te concerne pas...
    Paralléliser l'application n'est pas une option, mais une obligation de nos jours.

  4. #4
    Membre averti
    Profil pro
    Inscrit en
    Décembre 2006
    Messages
    45
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 45
    Par défaut
    Tout d'abords, merci pour vos réponses !

    @Spazou : J'ai bien ma liste de tache a faire, cependant ce ne sont que l'appel d'une methode d'un objet, et non une "Task" de la classe Task.

    Petit bout de code :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    // queue est ma liste de Releases déjà ajoutée au ThreadPool
    foreach (Release r in this._lstReleases)
       if (!queue.Contains(r))
       {
          queue.Add(r);
          Downloader dler = new Downloader(r);
          // On l'ajoute donc dans le threadpool, et on lance la fonction Start des qu'un thread est libre
          ThreadPool.QueueUserWorkItem(new WaitCallback(dler.Start));
        }
    Ce code est dans un timer, donc la liste est donc constamment modifiée et mise a jour.. Je n'ai donc aucun accès aux taches exécutée en cours..

    Par contre, je vais jeter un oeil sur INotifyPropertyChanged j'avoue encore ne pas connaitre

    @cinemania : Je viens de me renseigner un petit peu sur le modèle MVVM, ca m'a l'air intéressant il faudra que je m'y plonge complètement pour mieux tirer de ses avantages..
    Ta manière de faire dans ton exemple me semble excellente je vais tester ca en l'adaptant a mon code en faisant les bons binds, merci !

    Par contre, pour ta note, je n'ai jamais dit que je voulais limiter mon application a l'utilisation seule de 2 Threads. Pas du tout ! Je veux que mon application utilise le ThreadPool uniquement que lorsque je le demande (en utilisant donc des composants qui ne l'utilisent pas) afin de pouvoir contrôler le nombre de téléchargements simultanés.

    Si je limite a deux, sur un dual, c'est parceque c'est le
    minimum possible (d'ailleurs très facile a récupérer ce nombre), mais si l'utilisateur veut augmenter son nombre de téléchargement, par 20 par exemple, le ThreadPool devra s'adapter a lancer 20 threads.. Donc rien a voir avec une limitation que je veux imposer pour une quelconque machine, juste que comme je fais 1 téléchargement = 1 thread, il me faut contrôler au maximum l'utilisation de ce ThreadPool.

    Par ailleurs, je sais très bien que le Webclient utilise lui même un thread, fort heureusement sinon mon application bloquerait souvent vu le nombre de fois ou je lui fais appel, et j'utilise aussi plusieurs DispatcherTimer qui eux-même créent des Threads..

    Donc je ne veux pas limiter mon nombre d
    e Thread total, juste ceux utilisé par le ThreadPool .Net

    Merci encore pour ces pistes je vais creuser de ce cotes !

  5. #5
    Membre Expert
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    1 103
    Détails du profil
    Informations personnelles :
    Âge : 47
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 1 103
    Par défaut
    Ba de toute façon le nombre de téléchargement simultané est souvent limité par la bande passante du client plus qu'autre chose

    Effectivement le pattern MVVM ouvre des possibilités assez sympathiques pour l'écriture et pour l'abstraction, et éviter ainsi les usines à gaz

    Mais c'est vrai qu'au début c'est assez... perturbant. L'intérêt est que l'absence de code-behind et le couplage faible permettent de changer radicalement l'ui sans toucher le code source, puisque meme le nom des composants n'est pas connu par la logique.
    Si demain tu veux virer une listbox et mettre au chose de totalement différent, libre à toi... c'est donc une vraie révolution, et change complètement la façon d'appréhender les applications.

    En plus ce pattern est désormais possible avec Silverlight 4

  6. #6
    Membre averti
    Profil pro
    Inscrit en
    Décembre 2006
    Messages
    45
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 45
    Par défaut
    Parfait moi qui voulait me mettre a Silverlight 4 ca permettra de commencer en ayant une bonne manière de coder !

    Merci encore ;p

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

Discussions similaires

  1. Petit probleme dans un menu WPF
    Par Nikogram dans le forum Windows Presentation Foundation
    Réponses: 13
    Dernier message: 26/11/2009, 03h01
  2. petit probleme de rafraichissement de Composant
    Par kef1 dans le forum AWT/Swing
    Réponses: 4
    Dernier message: 03/03/2006, 12h21
  3. [Language]Petit problème de conception
    Par mitje dans le forum Langage
    Réponses: 15
    Dernier message: 20/12/2005, 23h57
  4. problème de conception : cycle
    Par FarookFreeman dans le forum Diagrammes de Classes
    Réponses: 13
    Dernier message: 20/10/2005, 10h15
  5. petit probleme dans une requte POSTGRE SQL
    Par ghis le fou dans le forum Requêtes
    Réponses: 5
    Dernier message: 08/09/2003, 13h51

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