26 votes

System.IO.IOException : Un périphérique attaché au système ne fonctionne pas C# .NET 4.0

J'ai construit une application C# qui lit et écrit des données à partir d'un port série. Le dispositif connecté au port série est un convertisseur USB-série FTDI qui communique avec le matériel via un module sans fil XBee. Le matériel teste les modules de batterie pour la capacité et la tension en régime permanent, etc. Ces tests prennent plusieurs jours.

De temps en temps, le port série semble ne plus répondre et déclenche une exception System.IO.IOException : Un périphérique attaché au système ne fonctionne pas.

Voici la trace de la pile :

at system.IO.Ports.InternalResources.WinIOError
at system.IO.Ports.SerialStream.EndWrite
at system.IO.Ports.SerialStream.Write
at system.IO.Ports.SerialPort.Write
at BatteryCharger.CommunicationClass.ApiTransmission

après cette erreur, une System.UnauthorizedAccessException: Access to the port is denied Cette erreur se produit à chaque fois que le logiciel essaie d'écrire sur le port et il ne se rétablit jamais jusqu'à ce que j'arrête le débogage et redémarre le logiciel pour que la même chose se reproduise quelques jours plus tard.

Comment puis-je empêcher cette erreur de se produire ou existe-t-il un moyen de récupérer avec succès ces erreurs dans le catch statement de l'erreur ?

Je lis le port série en continu dans un thread de travail en arrière-plan et j'écris à partir d'un thread différent.

J'ai également déjà essayé tous les éléments de gestion des erreurs héritées qui ont été suggérés sur ce forum, mais aucun d'entre eux ne semble faire de différence. L'erreur se produit sous Windows XP Pro SP3 32bit et Windows7 Pro 32bit.

Voici la CommunicationClass.cs - code de transmission en série.

    public static bool ApiTransmission(TXpacket transmission)
    {
        //clear all previous data that may have been in the buffer before doing a transmission
        Variables.dataParserPacket_buff.Clear();
        //TXpacket xbeetransmision = new TXpacket();
        byte[] packet = transmission.GeneratePacket();

        try
        {
            if (_serialPort.IsOpen)
            {
#if Console
                Log.write("TX-Packet: " + StringHandler.listToString(packet.ToList<byte>()));
#endif
                _serialPort.Write(packet, 0, packet.Length);
                Thread.Sleep(100);
            }
            else
            {
#if Console
                Log.write("serial port is closed");
#endif
                return false;
            }
        }
        catch (UnauthorizedAccessException ex)
        {
            MessageBox.Show(ex.ToString());
            Log.write("UnauthorizedAccessException");
        }
        catch (IOException ex)
        {
            MessageBox.Show(ex.ToString());
            Log.write("IOexception");
            //_serialPort.Close();
            //Thread.Sleep(100);
            //_serialPort.Open();
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
#if Console
            Log.write(ex.ToString());
#endif
        }
        return true;

    }

Voici comment j'initialise mon port série

    public CommunicationClass(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits)
    {
        _analysePacketBGW.DoWork += new DoWorkEventHandler(_analysePacketBGW_DoWork);
        _analysePacketBGW.WorkerReportsProgress = true;
        _analysePacketBGW.WorkerSupportsCancellation = true;

        _readBGW.DoWork += new DoWorkEventHandler(_readThread_DoWork);
        _readBGW.WorkerSupportsCancellation = true;
        _readBGW.WorkerReportsProgress = true;

        _parserStarterBGW.DoWork += new DoWorkEventHandler(_parserStarterThread_DoWork);
        _parserStarterBGW.WorkerSupportsCancellation = true;
        _parserStarterBGW.WorkerReportsProgress = true;
        if (_readBGW != null)
        {
            _readBGW.CancelAsync();
        }

        _serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);

        //SerialPortFixer.Execute(portName);
        //Thread.Sleep(1000);
        //using (_serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits))
        //{
        //    //_serialPort.Open();
        //}

        _serialPort.ErrorReceived += new SerialErrorReceivedEventHandler(_serialPort_ErrorReceived);

        _dataqueuepp = new ManualResetEvent(false);

        _serialPort.Open();
        _readBGW.RunWorkerAsync();
        _parserStarterBGW.RunWorkerAsync();
        CommunicationClass.PacketReceived += new DataPacketReceived(CommunicationClass_PacketReceived);
    }

Et le travailleur d'arrière-plan qui gère la lecture du port série

    void _readThread_DoWork(object sender, DoWorkEventArgs e)
    {
#if Console
        Log.write("Read()");
#endif
        while (!_readBGW.CancellationPending)
        {
            try
            {
                int message = _serialPort.ReadByte();
                try
                {
                    Variables.dataQueue.Enqueue(message);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message + "     " + message.ToString());
                }
                _dataqueuepp.Set();
                //Console.Write(String.Format("{0:X2}", message) + " ");
            }
            catch (TimeoutException) { Log.write("read timeout"); }
            catch (IOException) { Log.write("read IOException"); }
            catch (ThreadAbortException) { Log.write("read thread aborted"); }
            catch (Exception ex) { MessageBox.Show(ex.ToString()); }
            finally { }
        }
    }

Je vais maintenant réécrire le code pour lire et écrire sur le port série à partir du même fil pour voir si cela fait une différence.

EDIT

Sur la base des commentaires de Jim, j'ai ajouté ce qui suit à la déclaration Catch de l'IOException :

        catch (IOException ex)
        {
            MessageBox.Show(ex.ToString());
            Log.write("IOexception");
            _readBGW.CancelAsync();
            Thread.Sleep(100);
            _serialPort.Close();
            Thread.Sleep(100);
            _serialPort.Open();
            Thread.Sleep(100);
            _readBGW.RunWorkerAsync();
            _serialPort.Write(packet, 0, packet.Length);
        }

J'espère qu'en arrêtant le _serialPort.Read du travailleur d'arrière-plan, en fermant le port, en le rouvrant, en relançant le travailleur d'arrière-plan et en essayant d'écrire à nouveau la même commande, cela suffira à corriger cette erreur. La boîte à messages bloque toujours le code afin que je puisse voir quand l'erreur se produit et surveiller comment elle se rétablit.

Je n'aime pas les Parcheando logiciels comme celui-ci mais si ça marche alors ça marche.

EDIT 2

Après avoir ajouté le code ci-dessus, mon logiciel s'est à nouveau planté, mais maintenant il lance une "UnauthorizedAccessException - Access to the port is denied" lorsque j'appelle _serialPort.Close() ;

System.UnauthorizedAccessException was unhandled
Message=Access to the port is denied.
Source=System
StackTrace:
at System.IO.Ports.InternalResources.WinIOError(Int32 errorCode, String str)
at System.IO.Ports.InternalResources.WinIOError()
at System.IO.Ports.SerialStream.Dispose(Boolean disposing)
at System.IO.Stream.Close()
at System.IO.Ports.SerialPort.Dispose(Boolean disposing)
at System.IO.Ports.SerialPort.Close()
at BatteryCharger.CommunicationClass.ApiTransmission(TXpacket transmission) in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\CommunicationClass.cs:line 436
at BatteryCharger.CommunicationClass.tx1(TXpacket packet, String callingMethod) in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\CommunicationClass.cs:line 356
at BatteryCharger.XBee.setPin(String pinID, Byte status, XBee slave) in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\XBee.cs:line 215
at BatteryCharger.XBee.setPins(Int32 pins, XBee slave) in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\XBee.cs:line 177
at BatteryCharger.BatteryCharger.requestVoltage(Int32 block) in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\BatteryCharger.cs:line 595
at BatteryCharger.BatteryCharger.requestVoltages() in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\BatteryCharger.cs:line 612
at BatteryCharger.Form1.RunCommandOn(List`1 battList, Int32 command, Double lowerLimit, Double upperLimit) in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\Form1.cs:line 522
at BatteryCharger.Form1.chargeBlock(Int32 blockNr, Double lowerLimit, Double upperLimit) in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\Form1.cs:line 689
at BatteryCharger.Form1.<btnCheckCapacities_Click>b__13() in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\Form1.cs:line 619
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException: 

Qu'est-ce qui se passe ici ?

3voto

Sotirios Points 51

D'après mon expérience, 9 fois sur 10, cela se produit lorsqu'un autre thread (terminé ou non) n'a pas un accès exclusif au port matériel.

Essayez d'écrire un wrapper pour les opérations du port, le plus important étant l'ouverture/fermeture de celui-ci, en utilisant SyncLock . https://msdn.microsoft.com/en-us/library/3a86s51t.aspx

Dans le même ordre d'idées, je considère généralement que les try/catches contrôlant le matériel sont une mauvaise pratique, à moins qu'il n'y ait une gestion adéquate des exceptions.

La raison (qui pourrait s'appliquer ici) est que dans le cas où une exception est levée, le matériel se verrouille et, pire encore, l'exception masque la véritable cause de l'erreur ou des erreurs.

Dans le code ci-dessus, je vois la sortie des messages dans le style

DebugPrint(ex.Message); 

il serait bien mieux de le faire comme

DebugPrint(ex.tostring()); 

car cela exportera également la trace de la pile dans l'exception.

Ce que je ferais, c'est mettre en place un collecteur d'exceptions qui écrit ces exceptions dans un fichier texte (horodaté) quelque part sur l'ordinateur en cours d'exécution. Le suivi des données d'exception enregistrées (avec toutes les informations pertinentes) peut permettre de mieux comprendre pourquoi cela se produit exactement.

2voto

Mauro Raymondi Points 109

J'utilise intensivement la classe SerialPort pour communiquer en continu avec les automates via des adaptateurs USB pendant des mois sans aucune interruption. Donc je ne peux pas être d'accord avec ceux qui disent que la classe SerialPort .NET ne fonctionne pas. Essayez d'insérer la création de la classe dans le thread, voici un exemple de code avec BackgroundWorker.

    void ThreadEngine_DoWork(object sender, DoWorkEventArgs e)
    {
        // Do not access the form's BackgroundWorker reference directly.
        // Instead, use the reference provided by the sender parameter.
        BackgroundWorker objBackgroundWorker = sender as BackgroundWorker;

        try
        {
            mSerialPort = new SerialPort(GetPortName(mPortName), DefaultBaudRate, Parity.Even, 7, StopBits.Two);
            mSerialPort.Open();
            objBackgroundWorker.ReportProgress(0);

            while (objBackgroundWorker.CancellationPending == false)
            {
                if (IOScanner(objBackgroundWorker, false) == true)
                {
                    ScannerStationData();
                    IsReady = true;
                    IsError = false;
                }
                else
                {
                    IsReady = false;
                    IsError = true;
                }
                Thread.Sleep(1);
            }

            // Performs last scan before thread closing
            if (objBackgroundWorker.CancellationPending == true)
            {
                IOScanner(objBackgroundWorker, true);
            }

            mSerialPort.Close();
            mSerialPort = null;
            e.Result = null;
        }
        catch (Exception objErr)
        {
            string sMessage = string.Format("PlcService.ThreadEngine_DoWork Err={0}", objErr.Message);
            mLogSysService.AddItem(sMessage);
            IsError = true;
        }
    }

La méthode IOScanner appelle d'autres méthodes pour communiquer, comme la suivante.

    protected bool WriteDMWord(int iAddress, int[] aryValues)
    {
        bool bRetValue = true;
        try
        {
            mSerialPort.NewLine = "\r";
            mSerialPort.ReadTimeout = DefaultTimeout;
            string sTxData = HostLinkProtocol.BuildWriteDMWord(iAddress, aryValues);
            mSerialPort.WriteLine(sTxData);

            string sRxData = string.Empty;
            sRxData = mSerialPort.ReadLine();
            if (HostLinkProtocol.ParseWriteDMWord(sRxData) == true)
            {
                bRetValue = true;
            }
        }
        catch (Exception objErr)
        {
            Console.WriteLine("WriteDMWord [{0}]", objErr.Message);
            bRetValue = false;
        }
        return bRetValue;
    }

0voto

Voicu Marian Points 31

J'utilise un FTDI USB, et je communique avec lui sur Serial .net class, parfois sur WindowsXp j'ai ce genre d'exception, je le résous avec devcon.exe, je fais disable et enable et l'erreur n'apparait pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X