26

シリアル ポートからデータを読み書きする C# アプリケーションを作成しました。シリアル ポートに接続されているデバイスは、XBee ワイヤレス モジュールを介してハードウェアと通信する FTDI USB からシリアルへのコンバーターです。ハードウェアは、バッテリ モジュールの容量や定常状態の電圧などをテストします。これらのテストは完了するまでに数日かかります。

時々、シリアル ポートが応答を停止しているように見え、System.IO.IOException: A device attached to the system is not function エラーがスローされます。

スタック トレースは次のとおりです。

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

このエラーがスローされた後、エラーがスローSystem.UnauthorizedAccessException: Access to the port is deniedされ、ソフトウェアがポートに書き込もうとするたびにこのエラーが発生し、デバッグを停止してソフトウェアを再起動するまで、数日後に同じことが起こるまで回復しません。

このエラーの発生を防ぐにはどうすればよいですか、またはエラーの catch ステートメントでこれらのエラーから正常に回復する方法はありますか?

バックグラウンド ワーカー スレッドでシリアル ポートを継続的に読み取り、別のスレッドから書き込みを行っています。

また、このフォーラムで提案されている従来のエラー処理のビットとピースをすべて試しましたが、どれも違いがないようです。このエラーは、Windows XP Pro SP3 32 ビットおよび Windows7 Pro 32 ビットで発生します。

これが CommunicationClass.cs - シリアル伝送コードです。

    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;

    }

これは、シリアルポートを初期化する方法です

    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);
    }

シリアル ポートの読み取りを処理するバックグラウンド ワーカー

    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 { }
        }
    }

ここで、コードを書き直して、同じスレッドからシリアル ポートに読み書きするようにし、違いが生じるかどうかを確認します。

編集

Jim のコメントに基づいて、IOException Catch ステートメントに以下を追加しました。

        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);
        }

うまくいけば、バックグラウンド ワーカーの _serialPort.Read を停止し、ポートを閉じ、ポートを再度開き、バックグラウンド ワーカーを再度実行して、同じコマンドを再度書き込もうとすると、このエラーから正常に回復するのに十分です。MessageBox は引き続きコードをブロックするため、エラーがいつ発生したかを確認し、どのように回復するかを監視できます。

このようなソフトウェアにパッチを適用するのは好きではありませんが、それが機能する場合は機能します。

編集2

上記のコードを追加した後、ソフトウェアが再びクラッシュしましたが、_serialPort.Close(); を呼び出すと、「UnauthorizedAccessException - ポートへのアクセスが拒否されました」がスローされます。

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: 

ここで何が起こっているのですか?

4

3 に答える 3

3

私の経験では、10 回中 9 回、別のスレッド (終了しているかどうかに関係なく) がハードウェア ポートに排他的にアクセスしていない場合に発生します。

SyncLockを使用して、ポート操作、最も重要なのはポートの開閉のラッパーを作成してみてください。https://msdn.microsoft.com/en-us/library/3a86s51t.aspx

同様に、適切な例外処理がない限り、ハードウェアを制御する try/catches は一般的に悪い習慣だと考えています。

その理由 (ここで当てはまる可能性があります) は、例外がスローされた場合にハードウェアがロックされ、さ​​らに悪いことに、例外がエラーの真の原因を隠してしまうためです。

上記のコードでは、スタイルのメッセージの出力が表示されます

DebugPrint(ex.Message); 

このようにするのはかなり良いでしょう

DebugPrint(ex.tostring()); 

これは、例外のスタック トレースもエクスポートするためです。

私がすることは、これらの例外を、これが実行されているコンピューターのどこかにある (タイムスタンプ付きの) テキスト ファイルに書き込む例外ロガーを実装することです。ログに記録された例外データを (すべての関連情報と共に) 追跡すると、なぜこれが発生したのかをよりよく理解することができます。

于 2015-10-27T01:39:06.840 に答える
2

私は集中的に SerialPort クラスを使用して、USB アダプターを介して PLC と何ヶ月も途切れることなく継続的に通信しています。したがって、SerialPort .NET クラスが機能しないという意見には同意できません。クラスの作成をスレッドに挿入してみてください。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;
        }
    }

メソッドIOScannerは、次のような通信のために他のメソッドを呼び出します。

    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;
    }
于 2015-10-15T06:11:40.773 に答える