シリアルポートからデータを読み書きするC#アプリケーションを作成しました。シリアルポートに接続されたデバイスは、XBeeワイヤレスモジュールを介してハードウェアと通信するFTDI USBシリアルコンバーターです。ハードウェアは、バッテリーモジュールの容量や定常状態の電圧などをテストします。これらのテストは、完了するまでに数日かかります。
時々、シリアルポートが応答を停止し、System.IO.IOExceptionをスローするようです。システムに接続されているデバイスが機能していないというエラーが発生します。
スタックトレースは次のとおりです。
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 SP332ビットおよびWindows7Pro32ビットで発生します。
これが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 { }
}
}
ここで、同じスレッドからシリアルポートを読み書きするコードを書き直して、違いが生じるかどうかを確認します。
[〜#〜]編集[〜#〜]
ジムのコメントに基づいて、IOExceptionCatchステートメントに次を追加しました。
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:
ここで何が起こっているのですか?
私の経験では、これは10回のうち9回、別のスレッド(終了しているかどうかに関係なく)がハードウェアポートに排他的にアクセスできない場合に発生します。
SyncLockを使用して、ポート操作、最も重要なのは開閉のラッパーを記述してみてください。 https://msdn.Microsoft.com/en-us/library/3a86s51t.aspx
同様に、適切な例外処理がない限り、ハードウェアを制御するtry/catcは一般的に悪い習慣だと思います。
その理由(ここで当てはまる可能性があります)は、例外がスローされた場合、ハードウェアがロックされ、さらに悪いことに、例外がエラーの真の原因をマスクするためです。
上記のコードでは、メッセージの出力が次のスタイルで表示されます。
DebugPrint(ex.Message);
これを次のように行う方がはるかに良いでしょう
DebugPrint(ex.tostring());
これは、例外のスタックトレースもエクスポートするためです。
私がすることは、これが実行されているコンピューターのどこかにある(タイムスタンプ付きの)テキストファイルにそれらの例外を書き込む例外ロガーを実装することです。ログに記録された例外データ(およびすべての関連情報)を追跡することで、これが正確に発生する理由をよりよく理解できます。
私は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;
}
私はFTDIUSBを使用しており、Serial .netクラスで通信しています。WindowsXPではこの種の例外が発生することがあります。devcon.exeで解決し、無効化と有効化を実行しましたが、エラーは表示されませんでした。