Question

J'écris un service Windows pour la communication avec un lecteur de bande magnétique série et une carte relais (système de contrôle d'accès).

Je rencontre des problèmes lorsque le code cesse de fonctionner (j'obtiens IOExceptions) après qu'un autre programme a été "interrompu". Pour ce faire, ouvrez le même port série que mon service.

Une partie du code est la suivante:

public partial class Service : ServiceBase
{
    Thread threadDoorOpener;
    public Service()
    {
        threadDoorOpener = new Thread(DoorOpener);
    }
    public void DoorOpener()
    {
        while (true)
        {
            SerialPort serialPort = new SerialPort();
            Thread.Sleep(1000);
            string[] ports = SerialPort.GetPortNames();
            serialPort.PortName = "COM1";
            serialPort.BaudRate = 9600;
            serialPort.DataBits = 8;
            serialPort.StopBits = StopBits.One;
            serialPort.Parity = Parity.None;
            if (serialPort.IsOpen) serialPort.Close();
            serialPort.Open();
            serialPort.DtrEnable = true;
            Thread.Sleep(1000);
            serialPort.Close();
        }
    }
    public void DoStart()
    {
        threadDoorOpener.Start();
    }
    public void DoStop()
    {
        threadDoorOpener.Abort();
    }
    protected override void OnStart(string[] args)
    {
        DoStart();
    }
    protected override void OnStop()
    {
        DoStop();
    }
}

Mon exemple de programme démarre correctement le fil de travail et l'ouverture / la fermeture et le relèvement de DTR entraînent la mise sous tension de mon lecteur de bandes magnétiques (attendez 1 seconde), ainsi que sa fermeture (attendez 1 seconde), etc.

Si je lance HyperTerminal et que je me connecte au même port COM, HyperTerminal me dit que le port est actuellement utilisé. Si j’appuie plusieurs fois sur ENTER dans HyperTerminal, pour essayer de rouvrir le port, il réussira après quelques tentatives.

Cela a pour effet de provoquer des exceptions IO dans mon thread de travail, ce qui est attendu. Cependant, même si je ferme HyperTerminal, j'obtiens toujours la même exception IOException dans mon work-thread. Le seul remède consiste en fait à redémarrer l'ordinateur.

D'autres programmes (qui n'utilisent pas les bibliothèques .NET pour l'accès par port) semblent fonctionner normalement à ce stade.

Avez-vous des idées sur ce qui cause cela?

Était-ce utile?

La solution

@thomask

Oui, Hyperterminal active effectivement fAbortOnError dans le DCB de SetCommState, ce qui explique la plupart des exceptions IO envoyées par l'objet SerialPort. Certains ordinateurs personnels / ordinateurs de poche ont également des UART dont l'indicateur d'abandon en cas d'erreur est activé par défaut. Il est donc impératif que la routine d'initialisation d'un port série la supprime (ce que Microsoft a négligé de faire). J'ai récemment écrit un long article pour l'expliquer plus en détail (voir cette si cela vous intéresse).

Autres conseils

Vous ne pouvez pas fermer une autre connexion à un port, le code suivant ne fonctionnera jamais:

if (serialPort.IsOpen) serialPort.Close();

Comme votre objet n'a pas ouvert le port, vous ne pouvez pas le fermer.

Vous devez également fermer et supprimer le port série même après des exceptions

try
{
   //do serial port stuff
}
finally
{
   if(serialPort != null)
   {
      if(serialPort.IsOpen)
      {
         serialPort.Close();
      }
      serialPort.Dispose();
   }
}

Si vous souhaitez que le processus soit interruptible, vous devez vérifier si le port est ouvert, puis revenir en arrière pendant un certain temps, puis réessayer.

while(serialPort.IsOpen)
{
   Thread.Sleep(200);
}

Avez-vous essayé de laisser le port ouvert dans votre application et d'activer / désactiver DtrEnable, puis de fermer le port à la fermeture de votre application? c'est-à-dire:

using (SerialPort serialPort = new SerialPort("COM1", 9600))
{
    serialPort.Open();
    while (true)
    {
        Thread.Sleep(1000);
        serialPort.DtrEnable = true;
        Thread.Sleep(1000);
        serialPort.DtrEnable = false;
    }
    serialPort.Close();
}

Je ne connais pas bien la sémantique de la DTR, donc je ne sais pas si cela fonctionnerait.

Comment effectuer des communications asynchrones fiables

N'utilisez pas les méthodes de blocage, la classe d'assistance interne présente quelques bogues subtiles.

Utilisez APM avec une classe d'état de session, dont les instances gèrent un tampon et un curseur de tampon partagés entre des appels, ainsi qu'une implémentation de rappel qui enveloppe EndRead dans un try ... catch . En fonctionnement normal, la dernière chose que devrait faire le bloc try est de configurer le prochain rappel d'E / S avec chevauchement avec un appel à BeginRead () .

En cas de problème, catch doit invoquer de manière asynchrone un délégué à une méthode de redémarrage. L'implémentation de rappel doit se terminer immédiatement après le bloc catch afin que la logique de redémarrage puisse détruire la session en cours (l'état de la session est presque certainement corrompu) et créer une nouvelle session. La méthode de redémarrage ne doit pas être mise en œuvre sur la classe d'état de session, car cela l'empêcherait de détruire et de recréer la session.

Lorsque l'objet SerialPort est fermé (ce qui se produit lorsque l'application se ferme), il se peut qu'une opération d'E / S soit en attente. Dans ce cas, la fermeture de SerialPort déclenche le rappel et, dans ces conditions, EndRead lève une exception qui ne se distingue pas d'une shitfit de communications générales. Vous devez définir un indicateur dans votre état de session pour empêcher le comportement de redémarrage dans le bloc catch . Cela empêchera votre méthode de redémarrage d'interférer avec un arrêt naturel.

On peut compter sur cette architecture pour ne pas conserver l'objet SerialPort de manière inattendue.

La méthode de redémarrage gère la fermeture et la réouverture de l'objet de port série. Après avoir appelé Close () sur l'objet SerialPort , appelez Thread.Sleep (5) pour lui permettre de laisser tomber. Il est possible que quelque chose d'autre s'empare du port, alors soyez prêt à le gérer tout en le rouvrant.

Je pense avoir conclu que HyperTerminal ne fonctionne pas bien. J'ai effectué le test suivant:

  1. Démarrer mon service en mode "console", il commence à allumer / éteindre l'appareil (son voyant est allumé).

  2. Démarrez HyperTerminal et connectez-vous au port. L'appareil reste allumé (HyperTerminal soulève DTR) Mon service écrit dans le journal des événements qu'il ne peut pas ouvrir le port

  3. Arrêtez HyperTerminal, je vérifie qu'il est correctement fermé à l'aide du gestionnaire de tâches

  4. Le périphérique reste éteint (HyperTerminal a abaissé le DTR), mon application continue d'écrire dans le journal des événements, indiquant qu'elle ne peut pas ouvrir le port.

  5. Je lance une troisième application (celle avec laquelle je dois coexister) et lui indique de se connecter au port. Je le fais Aucune erreur ici.

  6. J'arrête l'application mentionnée ci-dessus.

  7. VOILA, mon service est réactivé, le port s'ouvre avec succès et le voyant s'allume / s'éteint.

J'ai essayé de changer le fil de travail de cette façon, avec exactement le même résultat. Une fois que HyperTerminal a réussi à "capturer le port", (pendant que mon thread est en veille), mon service ne pourra plus ouvrir le port.

public void DoorOpener()
{
    while (true)
    {
        SerialPort serialPort = new SerialPort();
        Thread.Sleep(1000);
        serialPort.PortName = "COM1";
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.StopBits = StopBits.One;
        serialPort.Parity = Parity.None;
        try
        {
            serialPort.Open();
        }
        catch
        {
        }
        if (serialPort.IsOpen)
        {
            serialPort.DtrEnable = true;
            Thread.Sleep(1000);
            serialPort.Close();
        }
        serialPort.Dispose();
    }
}

Ce code semble fonctionner correctement. Je l’ai testé sur ma machine locale dans une application console, en utilisant Procomm Plus pour ouvrir / fermer le port, et le programme continue de tourner.

    using (SerialPort port = new SerialPort("COM1", 9600))
    {
        while (true)
        {
            Thread.Sleep(1000);
            try
            {
                Console.Write("Open...");
                port.Open();
                port.DtrEnable = true;
                Thread.Sleep(1000);
                port.Close();
                Console.WriteLine("Close");
            }
            catch
            {
                Console.WriteLine("Error opening serial port");
            }
            finally
            {
                if (port.IsOpen)
                    port.Close();
            }
        }
    }

Cette réponse a tardé à devenir un commentaire ...

Je pense que lorsque votre programme est dans un Thread.Sleep (1000) et que vous ouvrez votre connexion HyperTerminal, HyperTerminal prend le contrôle du port série. Lorsque votre programme se réveille et tente d'ouvrir le port série, une exception IOException est levée.

Modifiez votre méthode et essayez de gérer l'ouverture du port différemment.

EDIT: A ce propos, vous devez redémarrer votre ordinateur lorsque votre programme échoue ...

C’est probablement parce que votre programme n’est pas vraiment fermé, ouvrez votre gestionnaire de tâches et voyez si vous pouvez trouver le service de votre programme. Veillez à arrêter toutes vos discussions avant de quitter votre application.

Existe-t-il une bonne raison de garder votre service de "propriétaire" " Le port? Regardez le service UPS intégré - une fois que vous lui avez dit qu'un onduleur est connecté à, disons, COM1, vous pouvez embrasser ce port au revoir. Je vous suggérerais de faire de même, à moins que le partage du port ne soit une exigence opérationnelle forte.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top