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 :

[C# 1.1] - Event & Thread


Sujet :

Windows Forms

  1. #1
    Rédacteur
    Avatar de Erakis
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Octobre 2003
    Messages
    523
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 523
    Points : 233
    Points
    233
    Par défaut [C# 1.1] - Event & Thread
    RBonjour à tous.

    J'ai dois concevoir un petit serveur TCP/IP multi-thread. Voilà le début :
    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
    public class TCPServer
    {
        // Déclaration des délégés
        public delegate void ClientConnected( int iClientID );
        public delegate void ClientDisconnected( int iClientID );
        public delegate void ClientErrorOccured( int iClientID, Exception e );
        public delegate void ClientDataReceived( int iClientID, byte[] data );
        
        // Déclaration des événements
        public event ClientConnected OnClientConnected;
        public event ClientDisconnected OnClientDisconnected;
        public event ClientErrorOccured OnClientErrorOccured;
        public event ClientDataReceived OnClientDataReceived;
    
        // Déclaration des membres privés
        private string m_IPAddress = "127.0.0.1";
        private int m_PortNumber = 2222;
        private bool m_bIsRunning = false;
        private TcpListener m_TCPListener = null;
        // ... 
    
        // Événements par défaut 
        private void ClientConnectedEvent( int iClientID )
        {
           if (OnClientConnected != null)
               OnClientConnected( iClientID );
        }
        private void ClientDataReceivedEvent( int iClientID, byte[] data )
        {
           if (OnClientDataReceived != null)
               OnClientDataReceived( iClientID, data );
        }
        // ... 
        
        // Constructeur 
        public TCPServer( string ipAddress, int iPortNumber )
        {
             m_PortNumber = iPortNumber;
             m_IPAddress = ipAddress;
        }
    
        // Démarrage du server 
        public void Start() 
        {
            Thread serverThread = new Thread( new ThreadStart( Listen ) );
            serverThread.Start();
        }
    
        // Main loop du serveur 
    
        private void Listen()
        {
    	m_bIsRunning = true;
    	m_TCPListener = new TcpListener( System.Net.IPAddress.Parse(m_IPAddress), m_PortNumber );
    	m_TCPListener.Start();
    	while( m_bIsRunning )
    	{
                      // Waiting for a connection
                      Socket clientSocket = null;
                      try
                      {
                         clientSocket = m_TCPListener.AcceptSocket();
                      }
                      catch (SocketException se )
                      {
                          ClientErrorOccuredEvent( -1, se );
                          continue;
                      }
    
                      // Client connecté
                      if (clientSocket.Connected)
                      {
                          m_ClientID++;
                          // Déclancher l'événement de connection
                          ClientConnectedEvent( newClientID );
    
                          // Créer un object ServerClientThread
                          ServerClientThread sct = new ServerClientThread( newClientID, clientSocket );
    					
    			
                         // Démarage du nouveau processus (gestion du client connecté)
                         Thread serverThread = new Thread( new ThreadStart( sct.Process ) );
                         serverThread.Start();
                         // ... 
                      }
          // ... 
          }
       }
    }
    Maintenant voici la classe Wrapper ServerClientThread :
    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
    public class ServerClientThread
    {
        // Déclaration des membres privés
        private bool m_bStopProcess = false;
        // ...
    
        // Constructeur 
        public ServerClientThread( int iClientID, Socket clientSocket )
        {
            m_ClientID = iClientID;
            m_ClientSocket = clientSocket;
        }
    
        // Point d'entré du thread 
        public void Process()
        {
           while (!m_bStopProcess)
           {
    // À partir d'ici je créer un NetworkStream et j'attend les
    entrées de donnée. Cependant, comment puis-je déclancher l'événement
    ClientDataReceived de la classe TCPServer qui à
    démarré ce thread ? Sous C++ j'aurais déclarer cette classe comme étant
    FRIEND mais en c# ??? 
           }        
        }
    }
    Alors voilà, je crois bien que ma question est claire
    Merci pour votre aide.
    Mieux vaut ne rien savoir que beaucoup savoir à moitié !
    Faite vous en pas avec la vie, personne en est sortie vivant !

  2. #2
    Rédacteur
    Avatar de abelman
    Inscrit en
    Février 2003
    Messages
    1 106
    Détails du profil
    Informations forums :
    Inscription : Février 2003
    Messages : 1 106
    Points : 2 629
    Points
    2 629
    Par défaut
    Salut

    Pas mal ton avatar ;-)

    Pour ton problème essaye de passer à chaque instance de la classe ClientServerThread l'objet TcpServer en cours

    Par exemple lors de la connexion d'un client dans ta fonction TcpServer.Listen

    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
                    
    // Client connecté
                      if (clientSocket.Connected)
                      {
                          m_ClientID++;
                          // Déclancher l'événement de connection
                          ClientConnectedEvent( newClientID );
    
                          // Créer un object ServerClientThread
                          ServerClientThread sct = new ServerClientThread( newClientID, clientSocket , this); // ça se passe ici
    					
    			
                         // Démarage du nouveau processus (gestion du client connecté)
                         Thread serverThread = new Thread( new ThreadStart( sct.Process ) );
                         serverThread.Start();
                         // ... 
                      }
    Comme ça dans la classe ClientServerThread (tu as mis TCPServer dans ton deuxième listing ;-)), tu pourras faire ç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
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public class TCPServer
    {
        // Déclaration des membres privés
        private bool m_bStopProcess = false;
        private TcpServer m_server;
        // ...
     
        // Constructeur 
        public ServerClientThread( int iClientID, Socket clientSocket, TcpServer server)
        {
            m_ClientID = iClientID;
            m_ClientSocket = clientSocket;
            m_ server = server;
        }
     
        // Point d'entré du thread 
        public void Process()
        {
           while (!m_bStopProcess)
           {
    // À partir d'ici je créer un NetworkStream et j'attend les
    entrées de donnée. Cependant, comment puis-je déclancher l'événement
    ClientDataReceived de la classe TCPServer qui à
    démarré ce thread ? Sous C++ j'aurais déclarer cette classe comme étant
    FRIEND mais en c# ??? 
                // Comme ça ???
                server.ClientDataReceived(m_ClientID, data);
           }        
        }
    }

  3. #3
    Rédacteur
    Avatar de Erakis
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Octobre 2003
    Messages
    523
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 523
    Points : 233
    Points
    233
    Par défaut
    Bonjour,

    J'y avais déjà pensé mais il y a un problème avec cette solution :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    private void ClientDataReceivedEvent( int iClientID, byte[] data )
    {
        if (OnClientDataReceived != null)
            OnClientDataReceived( iClientID, data );
    }
    Comme c'est un événement par défaut, elle est déclarée PRIVATE. Elle ne sera pas accessible depuis un autre classe.

    En terme de robusteste de classe, cette méthode devrait rester PRIVATE non ?
    Mieux vaut ne rien savoir que beaucoup savoir à moitié !
    Faite vous en pas avec la vie, personne en est sortie vivant !

  4. #4
    Rédacteur
    Avatar de abelman
    Inscrit en
    Février 2003
    Messages
    1 106
    Détails du profil
    Informations forums :
    Inscription : Février 2003
    Messages : 1 106
    Points : 2 629
    Points
    2 629
    Par défaut
    Pour moi cela ne pose pas de problème qu'elle soit publique, car elle ne fait rien d'autre que appeller un eventuel handler. Si l'event est public (c'est obligé), je ne vois pas pour quoi le handler par défaut ne le serais pas

    Si tu ne veux pas la rendre publique, tu peux toujours la mettre internal. Comme cela elle sera accessible uniquement aux classe de ton assembly (ta DLL je suppose).

    Par ailleurs, si jamais tu souhaites faire des classes qui dérivent de TCPServer, rend la public overridable ou internal protected overridable.

    @+

  5. #5
    Rédacteur
    Avatar de Erakis
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Octobre 2003
    Messages
    523
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 523
    Points : 233
    Points
    233
    Par défaut
    Merci beaucoup pour vos réponses.

    abelman je suis d'accord avec ton idée mais dans le cas ou cette classe pourrait se retrouver sur le marché, je ne trouve pas très PROPRE de publier des méthodes qui ne font que lancer les événements attachés.

    J'ai longtemps dévelopé avec C++ et la solution ici aurait été de placer la classe ServerClientThread comme étant FRIEND de la classe TCPServer. De cette manière, la classe ServerClientThread aurait eu accès au méthode privée de la classe TCPServer. Je sais que sous C# il existe le mot clé INTERNAL mais il n'a pas du tout la même utilité que le mot FRIEND du C++. Par conséquent, il peu arriver que certainnes classes soit développées sous le même assembly et que l'utilisateur nono puisse avoir accès à des fonctions qu'il ne devrait tout simplement pas voir... De toute façon, si l'on jette un petit coup d'oeil à la classe System.Windows.Forms.Button, je ne crois pas qu'on y voit le handler par défaut. En fait il est PROTECTED :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    protected override void OnClick (
        EventArgs e
    )
    Bref, j'ai trouvé un autre moyen plus PROPRE pour les circonstances. J'ai converti la classe ServerClientThread :

    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
    private struct ClientThreadObject
    {
        public Socket ClientSocket;
        public bool StopProcess;
        public bool ProcessStopped;
        public int ClientID;
        public ClientThreadObject(int iClientID, Socket clientSocket)
        {
            ClientID = iClientID;
            ClientSocket = clientSocket;
            StopProcess = false;
            ProcessStopped = true;
        }
    }
    J'ai créé la fonction Process() privée à ma classe TCPServer. Comme je développe avec le Framework 1.1 je ne peux pas passer directement des paramètres à mon Thread et je ne désire pas utiliser le ThreadPool pour des raisons personnelles. Donc j'ai ajouté une ArrayList à ma classe TCPServer, et à l'intérieur je conserve chacun des objects ClientThreadObject créé pour chaque Thread. Lorsque la fonction Process() est démaré pour un Thread, je récupère l'object courant par :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    public void Process()
    {
        ClientThreadObject ctl = (ClientThreadObject)m_ClientThreadList[m_ClientThreadList.Count - 1];
         // Loop du thread ici
    }
    Par la suite, j'ai une référence sur chaque Thread et du même coup mes méthodes public qui exécutaient mes événements ont pu redevenir PRIVÉ.

    Enfin ! Tout fonctionne très bien.
    Merci à tous
    Mieux vaut ne rien savoir que beaucoup savoir à moitié !
    Faite vous en pas avec la vie, personne en est sortie vivant !

  6. #6
    Rédacteur
    Avatar de abelman
    Inscrit en
    Février 2003
    Messages
    1 106
    Détails du profil
    Informations forums :
    Inscription : Février 2003
    Messages : 1 106
    Points : 2 629
    Points
    2 629
    Par défaut
    OK.

    Pour info, j'ai un peu la même classe. Mes handlers par défaut sont protected chez moi. La classe TCPServer est abstraite et ce sont les classes dérivées (TCPServerXXX) qui les redéfinissent éventuellement.

    Par ailleurs, pourquoi ne pas utiliser le ThreadPool? Sache que la solution 'un thread par client' devient lourde si tu as beaucoup de clients.(100 clients --> 100 threads)
    Rien ne t'empêche de créer ta propre classe ThreadPool pour palier au défauts de celle de .NET

    @+

  7. #7
    Rédacteur
    Avatar de Erakis
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Octobre 2003
    Messages
    523
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 523
    Points : 233
    Points
    233
    Par défaut
    Bonjour,

    Merci pour les renseignements
    Je crois que tu as compris pourquoi je n'utilise pas le ThreadPool, c'est le 25 connexions max. Pas très pratique pour un serveur... Par ailleurs, ma classe TCPServer s'occupe du nombre de connection maximal et dans le cas ou ce nombre serait dépassé, les connection supplémentaire seront refusés.
    Mais effectivement je pourrais construire mon propre ThreadPool mais je suis à cours de temps et je n'ai pas pris le temps de chercher des d'exemples sur le net.
    Mieux vaut ne rien savoir que beaucoup savoir à moitié !
    Faite vous en pas avec la vie, personne en est sortie vivant !

  8. #8
    Rédacteur
    Avatar de Erakis
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Octobre 2003
    Messages
    523
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 523
    Points : 233
    Points
    233
    Par défaut
    Merci abelman
    Comme tu as un peu la même classe, alors permet moi de te demander une autre question par rapport aux événements.

    Dans mon client j'ai un événement pour les données entrantes :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public delegate void ClientDataReceived(MemoryStream ms);
    public event ClientDataReceived OnClientDataReceived;
    ...
    public void ClientDataReceivedEvent(MemoryStream ms)
    {
        if (OnClientDataReceived != null)
            OnClientDataReceived(ms);
    }
    Maintenant, j'attache l'événement à une méthodes de ma fenêtre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    m_TcpClient = new TcpClient(...);
    m_TcpClient.OnClientDataReceived += new TCPClient.ClientDataReceived(m_TcpClient_OnClientDataReceived);
    m_TcpClient.ConnectTo( "127.0.0.1", 5555 );
    Maintenant voici dans la méthode appelée dans la form :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    private void m_TcpClient_OnClientDataReceived(MemoryStream ms)
    {
       m_TxtLog.Text += System.Text.Encoding.ASCII.GetString(ms.ToArray() );
    }
    Le problème ici c'est que dans mon TcpClient, j'ai un THREAD qui roule en background et qui attend que les données arrivent sur le Socket. Lorsque j'ai des données qui entrent, j'appel l'événement... Mais cela produit un "Cross-Thread" et gèle ma fenêtre ou parfois génère une exception. Donc à chaque fois je dois perdre du temps à avec des solutions du genre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    private delegate void AddLogEntryDelegate(string entry);
    ...
    private void AddLogEntry( string entry)
    {
       if (!m_TxtLog.InvokeRequired)
       {
            m_TxtLog.Text += entry;
       }
       else
       {
           BeginInvoke(new AddLogEntryDelegate(AddLogEntry), new object[] { entry});
       }
    }
    Solution ?
    Merci
    Mieux vaut ne rien savoir que beaucoup savoir à moitié !
    Faite vous en pas avec la vie, personne en est sortie vivant !

  9. #9
    Rédacteur
    Avatar de abelman
    Inscrit en
    Février 2003
    Messages
    1 106
    Détails du profil
    Informations forums :
    Inscription : Février 2003
    Messages : 1 106
    Points : 2 629
    Points
    2 629
    Par défaut
    Citation Envoyé par Erakis
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    private delegate void AddLogEntryDelegate(string entry);
    ...
    private void AddLogEntry( string entry)
    {
       if (!m_TxtLog.InvokeRequired)
       {
            m_TxtLog.Text += entry;
       }
       else
       {
           BeginInvoke(new AddLogEntryDelegate(AddLogEntry), new object[] { entry});
       }
    }
    Solution ?
    Merci
    La solution tu l'as trouvée tout seul
    Les fonctions d'un controle (Form en fait partie) ne peuvent être appellées que depuis le thread qui a crée le controle. Sinon il y a risque de CrossThread Exception. Le problème ne date pas de .NET. C'est déjà le cas en Win32.

    La solution c'est d'utiliser la méthode Invoke du controle.

    Si tu es en .NET 2.0 avec le composant BackGroundWorker il y a une propriété qui permet de s'affranchir des appels explicites d'Invoke.

  10. #10
    Rédacteur
    Avatar de Erakis
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Octobre 2003
    Messages
    523
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 523
    Points : 233
    Points
    233
    Par défaut
    Win32 ?
    Lorsque je développais avec MFC, je ne me rappel pas avoir eu des problème avec du Cross-Threading... Je mettais à jour ma boite de texte tout simplement et ma fenêtre ne freezait pas :S
    Biensur il fallait faire attention...

    J'utilise un composant pour la gestion téléphonique et ce dernier offre beaucoup d'événement. Comme par exemple lorsqu'un appel entre, ou un changement d'état de la ligne (ring, disconnected, calldrop), etc...

    Dans un projet ici je me sers de 2 lignes et lorsque les événements sont déclanchés (parfois en même temps), je met à jour mes boite de texte (log).
    Jusqu'ici je n'ai jamais rencontrer de problème avec cela et je suis sur et certain qu'il y a plus d'un Thread qui roule en arrière de ce composant.

    Mais comme tu dis, je vais faire un test avec InvokeRequired à savoir si tout provient du même Thread. Ce serait très surprenant

    Merci
    Mieux vaut ne rien savoir que beaucoup savoir à moitié !
    Faite vous en pas avec la vie, personne en est sortie vivant !

  11. #11
    Rédacteur
    Avatar de abelman
    Inscrit en
    Février 2003
    Messages
    1 106
    Détails du profil
    Informations forums :
    Inscription : Février 2003
    Messages : 1 106
    Points : 2 629
    Points
    2 629
    Par défaut
    Citation Envoyé par Erakis
    Win32 ?
    Lorsque je développais avec MFC, je ne me rappel pas avoir eu des problème avec du Cross-Threading... Je mettais à jour ma boite de texte tout simplement et ma fenêtre ne freezait pas :S
    Biensur il fallait faire attention...
    Coup de chance . Mon application MFC a fait deux ans en production avant de me faire des access violation. Je mettais à jour une grille toutes les secondes depuis un worker thread.

    Pour ton autre composant, à voir effectivement.

  12. #12
    Rédacteur
    Avatar de Erakis
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Octobre 2003
    Messages
    523
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 523
    Points : 233
    Points
    233
    Par défaut
    Merci pour le hint
    De plus, tu me dis que tu as déjà eu des problème relié à cela en MFC alors je n'ai d'autre choix que de te croire.

    Comme tu dis, sûrement que je n'ai pas eu de bad luck encore...
    Mieux vaut ne rien savoir que beaucoup savoir à moitié !
    Faite vous en pas avec la vie, personne en est sortie vivant !

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

Discussions similaires

  1. Events et threads
    Par HamzuS The Great dans le forum Développement Windows
    Réponses: 1
    Dernier message: 16/12/2010, 22h18
  2. Récupération d'event avec thread
    Par koyot3 dans le forum VB.NET
    Réponses: 1
    Dernier message: 17/09/2010, 08h25
  3. Création events pour thread
    Par syphon22 dans le forum Langage
    Réponses: 3
    Dernier message: 19/01/2010, 08h50
  4. Swing Event Dispatching Thread
    Par YeFFreY dans le forum EDT/SwingWorker
    Réponses: 14
    Dernier message: 23/06/2008, 10h53
  5. [C#]Thread, event/delegate et Form
    Par doccpu dans le forum Windows Forms
    Réponses: 28
    Dernier message: 01/08/2005, 18h35

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