シリアルマグストライプリーダーとリレーボード(アクセス制御システム)との通信用のWindowsサービスを書いています。
別のプログラムがサービスと同じシリアルポートを開いてプロセスを「中断」した後、コードが機能しなくなる(IOExceptionが発生する)問題が発生しました。
コードの一部は次のとおりです。
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();
}
}
私のサンプルプログラムは正常にワークスレッドを開始し、DTRを開いたり閉じたりして、Magストライプリーダーを起動(1秒待機)、シャットダウン(1秒待機)などします。
ハイパーターミナルを起動して同じCOMポートに接続すると、ハイパーターミナルはポートが現在使用中であると通知します。ハイパーターミナルでEnterキーを繰り返し押すと、ポートを再度開こうとすると、数回の再試行後に成功します。
これには、ワークスレッドでIOExceptionsが発生するという影響があります。ただし、ハイパーターミナルを閉じても、ワークスレッドで同じIOExceptionが発生します。唯一の解決策は、実際にはコンピュータを再起動することです。
他のプログラム(ポートアクセスに.NETライブラリを使用していない)は、この時点では正常に動作しているようです。
これを引き起こしているものに関するアイデアはありますか?
@thomask
はい、ハイパーターミナルは実際にはSetCommStateのDCBでfAbortOnErrorを有効にします。これは、SerialPortオブジェクトによってスローされるほとんどのIOExceptionについて説明します。一部のPC /ハンドヘルドには、デフォルトでオンになっているエラーフラグの中止を備えたUARTがあります。そのため、シリアルポートの初期化ルーチンがそれをクリアする必要があります(Microsoftはこれを無視しました)。私はこれをより詳細に説明するために最近長い記事を書きました(興味があれば this を参照してください)。
他の誰かのポートへの接続を閉じることはできません。次のコードは機能しません。
if (serialPort.IsOpen) serialPort.Close();
オブジェクトがポートを開かなかったため、ポートを閉じることはできません。
また、例外が発生した後でも、シリアルポートを閉じて廃棄する必要があります
try
{
//do serial port stuff
}
finally
{
if(serialPort != null)
{
if(serialPort.IsOpen)
{
serialPort.Close();
}
serialPort.Dispose();
}
}
プロセスを中断可能にしたい場合は、ポートが開いているかどうかを確認してから、しばらくの間オフにしてから、再試行してください。
while(serialPort.IsOpen)
{
Thread.Sleep(200);
}
アプリケーションでポートを開いたままにし、DtrEnableをオン/オフにして、アプリケーションが閉じるときにポートを閉じることを試みましたか?つまり:
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();
}
私はDTRのセマンティクスに詳しくないので、これが機能するかどうかはわかりません。
ブロッキングメソッドを使用しないでください。内部ヘルパークラスにはいくつかの微妙なバグがあります。
APMをセッション状態クラスで使用します。そのインスタンスは、呼び出し間で共有されるバッファーとバッファーカーソルを管理し、EndRead
を_try...catch
_でラップするコールバック実装です。通常の操作では、try
ブロックが最後に行うべきことは、BeginRead()
を呼び出して、次の重複したI/Oコールバックを設定することです。
うまくいかない場合、catch
は、restartメソッドへのデリゲートを非同期に呼び出す必要があります。コールバックの実装はcatch
ブロックの直後に終了する必要があります。これにより、再起動ロジックが現在のセッションを破壊し(セッション状態はほぼ確実に破損します)、新しいセッションを作成できます。再起動メソッドはnotをセッション状態クラスに実装する必要があります。これにより、セッションが破棄および再作成されなくなるためです。
SerialPortオブジェクトが閉じているとき(アプリケーションの終了時に発生します)は、保留中のI/O操作がある可能性があります。その場合、SerialPortを閉じるとコールバックがトリガーされ、これらの条件下ではEndRead
は一般的な通信のシットフィットと区別がつかない例外をスローします。 catch
ブロックの再起動動作を禁止するには、セッション状態にフラグを設定する必要があります。これにより、再起動メソッドが自然なシャットダウンを妨害するのを防ぎます。
このアーキテクチャは、SerialPortオブジェクトを予期せずに保持しないように頼ることができます。
Restartメソッドは、シリアルポートオブジェクトのクローズと再オープンを管理します。 SerialPort
オブジェクトでClose()
を呼び出した後、Thread.Sleep(5)
を呼び出して、手放す機会を与えます。何か他のものがポートをつかむ可能性があるので、ポートを再び開くときにこれに対処する準備をしてください。
私はこのようにワークスレッドを変更しようとしましたが、まったく同じ結果が得られました。ハイパーターミナルが「ポートのキャプチャ」に成功すると(スレッドがスリープしている間)、サービスでポートを再び開くことができなくなります。
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();
}
}
このコードは正常に動作するようです。私はローカルアプリケーションのコンソールアプリケーションで、Procomm Plusを使用してポートを開いたり閉じたりしてテストしましたが、プログラムは動き続けます。
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();
}
}
}
ハイパーターミナルがうまく機能しないという結論に達したと思います。次のテストを実行しました。
「コンソールモード」でサービスを開始すると、デバイスのオン/オフの切り替えが開始されます(LEDでわかります)。
ハイパーターミナルを起動し、ポートに接続します。デバイスはオンのままです(ハイパーターミナルはDTRを発生させます)私のサービスはイベントログに書き込み、ポートを開けません
ハイパーターミナルを停止します。タスクマネージャを使用して適切に閉じられていることを確認します
デバイスがオフのまま(ハイパーターミナルがDTRを下げた)、アプリがポートを開くことができないと言って、イベントログへの書き込みを続けます。
3番目のアプリケーション(共存させる必要のあるアプリケーション)を起動し、ポートに接続するように指示します。私はそうします。ここにはエラーはありません。
上記のアプリケーションを停止します。
VOILA、私のサービスが再び開始され、ポートが正常に開き、LEDがオン/オフになります。
サービスがポートを「所有」しないようにする正当な理由はありますか?組み込みのUPSサービスを見てください。たとえば、COM1にUPSが接続されていると伝えたら、そのポートに別れを告げることができます。ポートを共有するという強い運用上の要件がない限り、同じことを行うことをお勧めします。
この回答はコメントになるまで長くなりました...
プログラムがThread.Sleep(1000)にあり、ハイパーターミナル接続を開くと、ハイパーターミナルがシリアルポートを制御するようになると思います。プログラムが起動し、シリアルポートを開こうとすると、IOExceptionがスローされます。
メソッドを再設計し、ポートの開放を別の方法で処理してください。
編集:プログラムが失敗したときにコンピュータを再起動する必要があることについて...
それはおそらく、プログラムが実際に閉じられていないためです。タスクマネージャーを開いて、プログラムサービスが見つかるかどうかを確認してください。アプリケーションを終了する前に、必ずすべてのスレッドを停止してください。