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 :

Variables communes à plusieurs thread (multithreading)


Sujet :

C#

  1. #1
    Nouveau membre du Club
    Homme Profil pro
    Apprenti
    Inscrit en
    Juillet 2012
    Messages
    32
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Apprenti
    Secteur : Industrie

    Informations forums :
    Inscription : Juillet 2012
    Messages : 32
    Points : 37
    Points
    37
    Par défaut Variables communes à plusieurs thread (multithreading)
    Bonjour tout le monde!

    Cela fait plus d'une semaine que je me casse la tête à propos d'utiliser des variables communes entre plusieurs threads.
    Mon logiciel qui se situe en production, possède deux threads, un thread principal pour l'interface graphique, et un autre thread qui celui-ci contient un cycle infini. Ce cycle doit permettre de gérer le cycle automatique d'une machine contenant deux équipements Ethernet, un module d'entrées sorties Adam 6052, et un détecteur 3D IFM O3D200.
    De temps en temps j'ai un problème au niveau du cycle automatique, j'ai des ralentissements anormaux que je n'ai pas si au lieu d'utiliser un thread j'utilise un timer.
    Et j'ai un deuxième problème, quand je souhaite fermer mon logiciel, je lance un événement de fermeture qui est censé quitter la boucle infini du thread. La plupart du temps cet événement ne passe pas, et je pense que le problème est lié à l'accès aux variables communes aux deux threads.
    Auriez-vous une idée s'il vous plait? J'ai mis en pièce jointe le fichier du cycle automatique, et du fichier contenant les variables communes.

    Merci!
    Fichiers attachés Fichiers attachés

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

    Informations professionnelles :
    Activité : .NET / SQL SERVER

    Informations forums :
    Inscription : Avril 2007
    Messages : 14 175
    Points : 25 116
    Points
    25 116
    Par défaut
    (pas lu le code)

    pour arrêter le thread un simple booléen suffit, le setter quand on souhaite l'arrêter et la boucle le teste

    le mieux est de définir IsBackground à true avant de démarrer le thread, comme ca en cas d'arrêt du thread principal, le thread secondaire s'arrête

    en cas d'arrêt manuel, après avoir setté le booléen tu peux utiliser lethread.join(x ms) avec le temps du cycle évalué
    si le cycle est découpable, le mieux est alors de faire un while true et de tester le booléen entre chaque étape pour sortir plus vite

    les variables n'appartiennent pas à un thread, les seuls problèmes sont que les controles n'acceptent pas d'être modifiés à partir d'un thread autre que celui qui les a créé
    et l'autre c'est l'accès concurrentiel, par exemple si 2 threads incrémentent une variable en même temps il y a un risque de rater un incrément, ou encore un collection modifiée pendant son parcours par un autre thread

  3. #3
    Membre expert
    Avatar de Pragmateek
    Homme Profil pro
    Formateur expert .Net/C#
    Inscrit en
    Mars 2006
    Messages
    2 635
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Formateur expert .Net/C#
    Secteur : Conseil

    Informations forums :
    Inscription : Mars 2006
    Messages : 2 635
    Points : 3 958
    Points
    3 958
    Par défaut
    Citation Envoyé par Pol63 Voir le message
    pour arrêter le thread un simple booléen suffit, le setter quand on souhaite l'arrêter et la boucle le teste
    C'est "un peu plus" subtil que ça, je t'invite à consulter l'article complet que j'ai fait sur le sujet : http://pragmateek.com/synchronizatio...-abstractions/
    /!\ article très technique mais très intéressant /!\

    Pour te la faire courte j'ai corrigé :

    Citation Envoyé par Pragmateek
    pour arrêter le thread un simple booléen volatile suffit, le setter quand on souhaite l'arrêter et la boucle le teste

  4. #4
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 493
    Points
    5 493
    Par défaut
    Bonjour.

    Concernant le problème de l'arrêt, au vu de ton code cela ne peut venir que d'un deadlock. Celui-ci ne peut être causé que par un de tes appels à InvokeRequiired ou à OnProgressChanged. Je parie pour ma part sur le fait que ton thread Ui attend la fin du thread worker (avec un Join ?) tandis que ce dernier attend que le thread UI réponde avec OnProgressChanged.

    Concernant ton ralentissement avec un thread qui ne se produirait pas avec un timer, soit c'est une impression non-fondée, soit c'est une question de priorité de thread (ton thread worker aurait une priorité plus haute que le ThreadPool et consommerait davantage de ressources CPU, soit il serait au contraire trop bas alors qu'un autre thread attend un signal de celui-ci).

    Tu n'as pas besoin de ton ReaderWriterLock dans CMultiThreadingVariables, il te suffit de rendre ces variables volatiles:
    * Une assignation est toujours atomique donc pas de problème de cohérence même sans verrou dans ce cas.
    * Reste à s'assurer que les autres threads seront avertis de ce changement et mettront à jour leur cache si nécessaire pour voir la nouvelle valeur, ce qui peut être fait de façon plus économe par un modificateur "volatile". Un verrou force pour sa part une resynchronisation complète des caches processeurs à l'entrée et à la sortie du verrou, au lieu de simplement invalider la partie contenant la variable.
    * De la même façon, à moins d'avoir une communication inter-processus, tu n'as pas besoin de ton EventWaitHandle pour communiquer à tous les threads que ton code doit s'arrêter.

    Si tu as besoin d'une file d'attente d'éléments pour mettre en oeuvre le motif "producteur / consommateur", il y a une classe ConcurrentQueue qui fera très bien l'affaire, au lieu de t'embêter avec une synchronisation manuelle etcétéra.



    Accessoirement, si je peux me permettre et sans vouloir t'offenser, je pense que tu devrais remettre en cause plusieurs habitudes d'écriture de ton code : les nombres magiques sont punis de morts par le SEL depuis 1983 et les propriéts automatiques allégeraient joliment ton code. Je t'invite à considérer l'exemple suivant :
    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
    case 2010: // Lecture du capteur de niveau (positionnement de la palette sur la machine)
    	_descriptionProcessStep = String.Format(ApplicationConfig.LanguageManager.Translation.CAutoManuCycle_Step_2020, currentFloor);
    	_cycleStatus = ECycleStatus.Running;
     
    	if (CCommonVariables.MultiThreadingVariables.Adam6052_1.InvokeIfRequired(new Func<Boolean>(() => CCommonVariables.MultiThreadingVariables.Adam6052_1.DI[adam6052In])))
    		_nextProcessStep = 2020;
    	break;
     
    case 2020: // Fin d'autorisation de descente/montée de la palette
    	_descriptionProcessStep = String.Format(ApplicationConfig.LanguageManager.Translation.CAutoManuCycle_Step_2030, currentFloor);
    	_cycleStatus = ECycleStatus.Running;
     
    	if (CCommonVariables.MultiThreadingVariables.Adam6052_1.InvokeIfRequired(new Func<Boolean>(() => CCommonVariables.MultiThreadingVariables.Adam6052_1.OutputOff(_adam6052_Out_AutorizeDescent))) &&
    		!CCommonVariables.MultiThreadingVariables.Adam6052_1.InvokeIfRequired(new Func<Boolean>(() => CCommonVariables.MultiThreadingVariables.Adam6052_1.DO[_adam6052_Out_AutorizeDescent])))
    		_nextProcessStep = 2030;
    	break;

    Il peut déjà être réécrit simplement en :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    case Step.AdjustPalett:
    	DescriptionProcessStep = String.Format(Translation.CAutoManuCycle_AdjustPalett, currentFloor);
    	CycleStatus = ECycleStatus.Running;
     
    	if (Input) NextProcessStep = Step.StopPalett;
    	break;
     
    case Step.StopPalett:
    	DescriptionProcessStep = String.Format(CAutoManuCycle_StopPalett, currentFloor);
    	CycleStatus = ECycleStatus.Running;
     
    	if (OffOutput == AdamOutput.AutorizeDescent && Output != AdamOutput.AutorizeDescent) NextProcessStep = Step.FillFloor;
    	break;

    Et après ça tu pourrais regrouper les tests d'arrêts, de passage en mode manuel et autres changements d'états extrinsèques en une méthode ContinueAutoCycle afin d'avoir un code rendant ton flux de contrôle plus explicite pour le futur lecteur :
    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
    bool AdjustPalett()
    {
      	 DescriptionProcessStep = String.Format(Translation.CAutoManuCycle_AdjustPalett, currentFloor);
     
            while(ContinueAutoCycle())
            {
        	        if (Input) return StopPalett();
            }
            return false;
    }
     
    bool StopPalett()
    {
           	DescriptionProcessStep = String.Format(Translation.CAutoManuCycle_StopPalett, currentFloor);
     
            while(ContinueAutoCycle())
            {
     	        if (OffOutput == AdamOutput.AutorizeDescent && Output != AdamOutput.AutorizeDescent) return FillFloor();
            }
            return false;
    }

  5. #5
    Nouveau membre du Club
    Homme Profil pro
    Apprenti
    Inscrit en
    Juillet 2012
    Messages
    32
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Apprenti
    Secteur : Industrie

    Informations forums :
    Inscription : Juillet 2012
    Messages : 32
    Points : 37
    Points
    37
    Par défaut
    Bonjour,

    Je viens de tester vos propositions. J'ai passé mes objets communs en volatile, ce qui n'a rien changé, j'ai toujours un inter-blocage entre les threads.
    La seule solution qui règle mon problème c'est de mettre une durée dans le Join(20), mais ce n'est pas une solution propre, ça force l'arrêt alors que je préfèrerai que le thread se termine normalement.
    J'ai mis en commentaire l'événement OnProcessChanged, mais j'ai toujours un inter-blocage. Le problème doit donc être lié avec InvokeRequired.
    Mais comment le fait d'utiliser InvokeRequired pourrait bloquer mes deux threads? Ce n'est pas censé s'exécuter de manière synchrone avec le thread créateur?

  6. #6
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 493
    Points
    5 493
    Par défaut
    Bonjour.

    Utiliser volatile visait seulement à simplifier le code, cela ne pouvait pas résoudre ton problème.

    Concernant celui-ci, c'est assez simple: le thread UI a une file d'attente et ton code est toujours appelé depuis une boucle qui lit cette file d'attente. Caricaturalement ça donnerait :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    pour chaque message 
    {
     ... Si message.IsClick alors invoquer handler du clic ... 
     ... Si InvokeRequired en attente alors invoquer action puis réveiller appelant. ...
    }

    Donc d'un côté tu as ton thread UI qui s'est mis en sommeil en demandant à l'OS d'être réveillé quand le worker thread aura terminé. De l'autre tu as ton worker thread qui a placé un message en file d'attente de l'UI et qui a demandé à l'OS d'être réveillé quand ton thread UI l'aura terminer.


    Tu as donc deux choix :
    * Tu supprimes le InvokeRequired lors de la fermeture. Par exemple tu détruis d'abord l'UI avant Join et le InvokeRequired échouera avec une exception.
    * Tu annules la fermeture de l'appli, tu ordonnes au worker thread de s'arrêter, puis tu trouves un autre moyen de détecter la fin du worker thread et alors seulement tu fermes à nouveau l'application.

  7. #7
    Nouveau membre du Club
    Homme Profil pro
    Apprenti
    Inscrit en
    Juillet 2012
    Messages
    32
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Apprenti
    Secteur : Industrie

    Informations forums :
    Inscription : Juillet 2012
    Messages : 32
    Points : 37
    Points
    37
    Par défaut
    Le problème est résolu, j'ai mis le bout de code qui me bloquait en asynchrone, pour éviter l'inter-blocage des threads, à présent l'arrêt du programme se fait normalement.
    J'ai mis l'exemple ci-dessous, merci pour votre aide !

    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
    CCommonVariables.MultiThreadingVariables.AutoManuCycle.StopCycle = true;
    CCommonVariables.MultiThreadingVariables.AutoManuCycle.OnProcessStepChanged -= AutoManuCycle_OnProcessStepChanged;
    await Task.Run(() =>
    {
        // Execution du test d'état du cycle en asynchrone car bloquante dans le thread principal (temps d'attente avant que la demande
        // d'arrêt a bien été prise en compte
        while (CCommonVariables.MultiThreadingVariables.AutoManuCycle.CycleStatus != ECycleStatus.Stopped)
            Thread.Sleep(10);
    });                    
     
    // Demande aux threads de finir leur cycle en cours
    if ((_threadAutoManuCycle != null) && _threadAutoManuCycle.IsAlive)
    {
        CCommonVariables.MultiThreadingVariables.AutoManuCycle.ExitThread();
        _threadAutoManuCycle.Join();
    }

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

Discussions similaires

  1. variables communes à plusieurs thread
    Par Z-fly dans le forum C
    Réponses: 10
    Dernier message: 10/09/2008, 08h59
  2. [débutant] partage de variable par plusieurs threads
    Par dahtah dans le forum Débuter avec Java
    Réponses: 6
    Dernier message: 06/03/2007, 13h34
  3. variable commune à plusieurs pages en JSP
    Par soumou dans le forum Servlets/JSP
    Réponses: 4
    Dernier message: 03/09/2006, 23h50
  4. Réponses: 5
    Dernier message: 20/09/2005, 22h48

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