2009-01-20 13 views
15

J'essaie d'utiliser la propriété .BaseStream du .NET2.0 SerialPort pour effectuer des lectures et des écritures asynchrones (BeginWrite/EndWrite, BeginRead/EndRead). J'ai un certain succès dans ce domaine, mais après un certain temps, je remarque (en utilisant Process Explorer) une augmentation très progressive des poignées que l'application utilise, et parfois un thread supplémentaire, qui augmente également le nombre de poignées.Comment utiliser correctement le port série .NET2.0 .BaseStream pour l'opération asynchrone

Le taux de changement de contexte augmente également chaque fois qu'un nouveau thread apparaît.

L'application envoie constamment 3 octets à un dispositif PLC, et obtient quelque 800 octets en retour, et ce, à une vitesse de transmission de 57600.

Le delta initial cDébranchez (encore une fois, à partir de l'explorateur de processus) est autour de 2500, ce qui semble très élevé de toute façon. Chaque fois qu'un nouveau thread apparaît, cette valeur augmente et la charge du processeur augmente en conséquence. J'espère que quelqu'un pourrait avoir fait quelque chose de similaire, et peut m'aider, ou même dire 'Au nom de Dieu, ne le faites pas de cette façon.'

Dans le code ci-dessous, 'this._stream' est obtenu à partir de SerialPort.BaseStream, et CommsResponse est une classe que j'utilise comme objet d'état IAsyncresult.

Ce code est commun à une connexion TCP que je fais comme alternative à l'utilisation du port série, (j'ai une classe de base CommsChannel, avec un canal série et TCP dérivé) et il n'a aucun de ces problèmes. Je suis raisonnablement optimiste qu'il n'y a rien de mal avec la classe CommsResponse.

Commentaires reçus avec reconnaissance.

/// <summary> 
    /// Write byte data to the channel. 
    /// </summary> 
    /// <param name="bytes">The byte array to write.</param> 
    private void Write(byte[] bytes) 
    { 
     try 
     { 
      // Write the data to the port asynchronously. 
      this._stream.BeginWrite(bytes, 0, bytes.Length, new AsyncCallback(this.WriteCallback), null); 
     } 
     catch (IOException ex) 
     { 
      // Do stuff. 
     } 
     catch (ObjectDisposedException ex) 
     { 
      // Do stuff. 
     } 
    } 

    /// <summary> 
    /// Asynchronous write callback operation. 
    /// </summary> 
    private void WriteCallback(IAsyncResult ar) 
    { 
     bool writeSuccess = false; 

     try 
     { 
      this._stream.EndWrite(ar); 
      writeSuccess = true; 
     } 
     catch (IOException ex) 
     { 
      // Do stuff. 
     } 

     // If the write operation completed sucessfully, start the read process. 
     if (writeSuccess) { this.Read(); } 
    } 

    /// <summary> 
    /// Read byte data from the channel. 
    /// </summary> 
    private void Read() 
    { 
     try 
     { 
      // Create new comms response state object. 
      CommsResponse response = new CommsResponse(); 

      // Begin the asynchronous read process to get response. 
      this._stream.BeginRead(this._readBuffer, 0, this._readBuffer.Length, new AsyncCallback(this.ReadCallback), response); 
     } 
     catch (IOException ex) 
     { 
      // Do stuff. 
     } 
     catch (ObjectDisposedException ex) 
     { 
      // Do stuff. 
     } 
    } 

    /// <summary> 
    /// Asynchronous read callback operation. 
    /// </summary> 
    private void ReadCallback(IAsyncResult ar) 
    { 
     // Retrieve the comms response object. 
     CommsResponse response = (CommsResponse)ar.AsyncState; 

     try 
     { 
      // Call EndRead to complete call made by BeginRead. 
      // At this point, new data will be in this._readbuffer. 
      int numBytesRead = this._stream.EndRead(ar); 

      if (numBytesRead > 0) 
      { 
       // Create byte array to hold newly received bytes. 
       byte[] rcvdBytes = new byte[numBytesRead]; 

       // Copy received bytes from read buffer to temp byte array 
       Buffer.BlockCopy(this._readBuffer, 0, rcvdBytes, 0, numBytesRead); 

       // Append received bytes to the response data byte list. 
       response.AppendBytes(rcvdBytes); 

       // Check received bytes for a correct response. 
       CheckResult result = response.CheckBytes(); 

       switch (result) 
       { 
        case CheckResult.Incomplete: // Correct response not yet received. 
         if (!this._cancelComm) 
         { 
          this._stream.BeginRead(this._readBuffer, 0, this._readBuffer.Length, 
           new AsyncCallback(this.ReadCallback), response); 
         } 
         break; 

        case CheckResult.Correct: // Raise event if complete response received. 
         this.OnCommResponseEvent(response); 
         break; 

        case CheckResult.Invalid: // Incorrect response 
         // Do stuff. 
         break; 

        default: // Unknown response 
         // Do stuff. 
         break; 
       } 
      } 
      else 
      { 
       // Do stuff. 
      } 
     } 
     catch (IOException ex) 
     { 
      // Do stuff. 
     } 
     catch (ObjectDisposedException ex) 
     { 
      // Do stuff. 
     } 
    } 

Répondre

5

Quelques suggestions:

, vous pourriez avoir un fonctionnement synchrone Write Puisque vous n'envoyez 3 octets. Le retard ne serait pas vraiment un problème.

Ne créez pas non plus un nouvel AsyncCallback tout le temps. Créez un AsyncCallback en lecture et en écriture et utilisez-le dans chaque appel de début.

+1

Merci pour votre réponse. Bonne suggestion concernant la création des rappels. Je l'ai essayé, mais le taux de commutation handle/thread/contexte augmente encore, bien qu'il semble augmenter à un rythme plus lent, donc une amélioration. – Andy

4

Pas besoin du tout pour BeginWrite. Vous n'envoyez que 3 octets, ils s'insèrent facilement dans le tampon de transmission, et vous êtes toujours sûr que le tampon est vide lorsque vous envoyez le jeu suivant. Gardez à l'esprit que les ports série sont beaucoup plus lents que les connexions TCP/IP. Il est très probable que vous finissiez par appeler BeginRead() pour chaque octet que vous recevez. Cela donne un bon entraînement au pool de threads, vous verriez certainement beaucoup de changements de contexte. Pas si sûr de la consommation de poignée. Assurez-vous de tester cela sans le débogueur attaché.

Essayer des données au lieu de BeginRead() est certainement quelque chose que vous devriez essayer. Tirez au lieu de pousser, vous utiliserez un thread threadpool quand il se passe quelque chose au lieu d'en avoir toujours un actif.

+0

Je prends le point en ce qui concerne BeginWrite, ce n'est pas vraiment nécessaire. BeginRead ne se déclenche pas pour chaque octet, mais il se déclenche assez fréquemment. Peut-être que je pourrais utiliser DataReceived et passer les octets dans un flux de mon propre pour garder la classe cohérente? Je suis en train de tester une version 'Release' aussi, BTW. – Andy

+0

@Hans: Comme Andy l'a dit, 'BeginRead' /' EndRead' gère facilement plusieurs octets par appel (en fonction du paramètre de délai d'attente). Et S.IO.P.SerialPort bloque un thread threadpool à tout moment afin de détecter les données et de déclencher DataReceived, de sorte que vous imaginez l'utilisation des ressources inférieure. En réalité, il y a moins d'appels de noyau nécessaires pour 'BeginRead' /' EndRead' qu'avec la solution detect-activity-then-read. –

0

Est-il possible de prendre les données venant du port série et de les envoyer directement dans un fichier? Avec des débits en bauds élevés (1 MégaBaud), il est difficile de gérer cette quantité de données non-stop.

0

La réponse de l'appareil est-elle toujours de taille fixe? Si oui, essayez d'utiliser SerialPort.Read et passez la taille du paquet. Cela bloquera, alors combinez-le avec DataReceived.Mieux encore, si la réponse se termine toujours par le (s) même (s) caractère (s), et que cette signature de fin est garantie unique dans le paquet, définissez la propriété NewLine et utilisez ReadLine. Cela vous immunisera contre les futurs changements de taille de paquet.

+0

Les tailles de paquet sont malheureusement variables. – Andy

+0

Bummer. Alors, comment savez-vous quand vous avez reçu un paquet complet? – mtrw

+0

Le paquet contient les octets d'en-tête et de terminaison, avec la longueur du paquet et la somme de contrôle codée en son sein. – Andy