Question

J'ai un très mauvais morceau de code Serial Port qui est très instable.

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Thread.Sleep(100);
    while (port.BytesToRead > 0)
    {
        var count = port.BytesToRead;

        byte[] buffer = new byte[count];

        var read = port.Read(buffer, 0, count);

        if (DataEncapsulator != null)
            buffer = DataEncapsulator.UnWrap(buffer);


       var response = dataCollector.Collect(buffer);

       if (response != null)
       {
           this.OnDataReceived(response);
       }

       Thread.Sleep(100);
    }    
}

Si je supprime l'un des appels Thread.Sleep (100), le code cesse de fonctionner.

Bien sûr, cela ralentit vraiment les choses et si beaucoup de flux de données sont entrés, cela cesse de fonctionner aussi bien à moins que je ne rende le sommeil encore plus grand. (Cesse de fonctionner comme dans une impasse pure)

Veuillez noter que DataEncapsulator et DataCollector sont des composants fournies par le MEF, mais leurs performances sont plutôt bonnes.

La classe a une méthode Listen () qui permet à un travailleur d’arrière-plan de recevoir des données.

public void Listen(IDataCollector dataCollector)
{
    this.dataCollector = dataCollector;
    BackgroundWorker worker = new BackgroundWorker();

    worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    worker.RunWorkerAsync();
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    port = new SerialPort();

    //Event handlers
    port.ReceivedBytesThreshold = 15;
    port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);

    ..... remainder of code ...

Les suggestions sont les bienvenues!

Mise à jour: * Juste une petite note sur ce que font les classes IDataCollector. Il n'y a aucun moyen de savoir si tous les octets des données qui ont été envoyées sont lus en une seule lecture. Donc, chaque fois que les données sont lues, il est passé au DataColllector qui retourne vrai quand un complet et Un message de protocole valide a été reçu. Dans ce cas-ci, il suffit vérifie l'octet de synchronisation, la longueur, le crc et l'octet de queue. Le vrai travail est fait plus tard par d'autres classes. *

Mise à jour 2: J'ai remplacé le code maintenant, comme suggéré, mais il y a toujours un problème:

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
        var count = port.BytesToRead;

        byte[] buffer = new byte[count];

        var read = port.Read(buffer, 0, count);

        if (DataEncapsulator != null)
            buffer = DataEncapsulator.UnWrap(buffer);

        var response = dataCollector.Collect(buffer);

        if (response != null)
        {
            this.OnDataReceived(response);
        }     
}

Vous voyez que cela fonctionne bien avec une connexion rapide et stable. Mais OnDataReceived n'est PAS appelé à chaque fois que des données sont reçues. (Voir la documentation MSDN pour plus d'informations). Donc, si les données sont fragmentées et vous ne lisez qu'une fois dans les données de l'événement sont perdues.

Et maintenant je me souviens pourquoi j'avais la boucle en premier lieu, parce que il doit en fait lire plusieurs fois si la connexion est lente ou instable.

Évidemment, je ne peux pas revenir à la solution de la boucle while, que puis-je faire?

Était-ce utile?

La solution

Mon premier souci avec le fragment de code original while est l’allocation constante de mémoire pour le tampon d’octets. Mettre un " nouveau " Cette instruction va spécifiquement au gestionnaire de mémoire .NET pour allouer de la mémoire pour la mémoire tampon, tout en prenant la mémoire allouée lors de la dernière itération et en la renvoyant dans le pool inutilisé pour une éventuelle récupération de place. Cela semble être une énorme somme de travail à accomplir dans une boucle relativement étroite.

Je suis curieux de l’amélioration des performances que vous obtiendriez en créant cette mémoire tampon au moment du design avec une taille raisonnable, disons 8 Ko, de sorte que vous n’ayez pas toute cette allocation de mémoire, cette désaffectation et cette fragmentation. Cela aiderait-il?

private byte[] buffer = new byte[8192];

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Thread.Sleep(100);
    while (port.BytesToRead > 0)
    {
        var count = port.BytesToRead;

        var read = port.Read(buffer, 0, count);

        // ... more code   
    }
}

Mon autre préoccupation concernant la réallocation de ce tampon à chaque itération de la boucle est que la réallocation peut ne pas être nécessaire si le tampon est déjà suffisamment grand. Considérez ce qui suit:

  • Itération en boucle 1: 100 octets reçus; allouer un tampon de 100 octets
  • Itération de boucle 2: 75 octets reçus; allouer un tampon de 75 octets

Dans ce scénario, vous n'avez pas vraiment besoin de réaffecter la mémoire tampon, car la mémoire tampon de 100 octets allouée dans l'Itération de boucle 1 est amplement suffisante pour gérer les 75 octets reçus dans l'Iteration de boucle 2. Il n'est pas nécessaire détruire le tampon de 100 octets et créer un tampon de 75 octets. (C’est évidemment inutile si vous créez simplement le tampon de manière statique et que vous le déplacez complètement hors de la boucle.)

Sur une autre tangente, je pourrais suggérer que la boucle DataReceived ne concerne que la réception des données. Je ne sais pas ce que font ces composants MEF, mais je me demande si leur travail doit être effectué dans la boucle de réception des données. Est-il possible que les données reçues soient placées dans une sorte de file d'attente et que les composants MEF puissent les récupérer? Je souhaite que la boucle DataReceived soit aussi rapide que possible. Peut-être que les données reçues peuvent être placées dans une file d'attente afin de pouvoir retourner au travail et recevoir davantage de données. Vous pouvez éventuellement configurer un autre thread pour surveiller les données arrivant dans la file d'attente et laisser les composants MEF extraire les données à partir de là et faire leur travail à partir de là. C’est peut-être plus de codage, mais cela peut aider la boucle de réception de données à être aussi réactive que possible.

Autres conseils

Et ça peut être si simple ...

Soit vous utilisez le gestionnaire DataReceived mais sans boucle et certainement sans Sleep (), lisez quelles données sont prêtes et transmettez-les quelque part (dans une file d'attente ou un flux de mémoire),

ou

Commencez un thread (BgWorker) et effectuez un (blocage) serialPort1.Read (...), puis appuyez ou assemblez les données que vous obtenez.

Modifier:

D'après ce que vous avez publié, je dirais: supprimez le gestionnaire d'événements et lisez simplement les octets à l'intérieur de Dowork (). Cela vous permet de spécifier la quantité de données souhaitée, à condition qu’elle soit (beaucoup) plus petite que ReadBufferSize.

Edit2, concernant Update2:

Vous serez toujours mieux avec une boucle while dans un BgWorker, sans utiliser l'événement du tout. La manière simple:

byte[] buffer = new byte[128];  // 128 = (average) size of a record
while(port.IsOpen && ! worker.CancelationPending)
{
   int count = port.Read(buffer, 0, 128);
   // proccess count bytes

}

Maintenant, peut-être que vos enregistrements ont une taille variable et que vous ne voulez pas attendre les 126 prochains octets pour en terminer un. Vous pouvez régler cela en réduisant la taille de la mémoire tampon ou en définissant un ReadTimeOut. Pour obtenir un grain très fin, vous pouvez utiliser port.ReadByte (). Depuis que le ReadBuffer se lit, cela ne ralentit pas vraiment.

Si vous souhaitez écrire les données dans un fichier et que le port série s’arrête de temps en temps, c’est un moyen simple de le faire. Si possible, créez une mémoire tampon suffisamment grande pour contenir tous les octets que vous prévoyez de placer dans un seul fichier. Ensuite, écrivez le code dans votre gestionnaire d'événements datareceived comme indiqué ci-dessous. Ensuite, lorsque vous en avez l'occasion, écrivez l'intégralité de la mémoire tampon dans un fichier, comme indiqué ci-dessous. Si vous devez lire FROM dans votre mémoire tampon alors que le port série lit dans votre mémoire tampon, essayez d’utiliser un objet flux en mémoire tampon pour éviter les blocages et les conditions de concurrence.

private byte[] buffer = new byte[8192]; 
var index = 0;
void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{    
    index += port.Read(buffer, index, port.BytesToRead);
} 

void WriteDataToFile()
{
    binaryWriter.Write(buffer, 0, index); 
    index = 0;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top