26

シリアル磁気ストライプ リーダーおよびリレー ボード (アクセス制御システム) と通信するための Windows サービスを作成しています。

別のプログラムが自分のサービスと同じシリアル ポートを開いてプロセスを「中断」した後、コードが動作しなくなる (IOExceptions が発生する) 問題が発生します。

コードの一部は次のとおりです。

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-stripe リーダーが起動 (1 秒待機)、シャットダウン (1 秒待機) などします。

HyperTerminal を起動して同じ COM ポートに接続すると、HyperTerminal はポートが現在使用中であることを通知します。ハイパーターミナルで ENTER を繰り返し押して、ポートを再度開いてみると、数回再試行すると成功します。

これにより、作業スレッドで IOExceptions が発生する効果があり、これは予想どおりです。ただし、HyperTerminal を閉じても、ワークスレッドで同じ IOException が発生します。唯一の解決策は、実際にコンピューターを再起動することです。

他のプログラム (ポート アクセスに .NET ライブラリを使用していないプログラム) は、この時点では正常に動作しているようです。

これを引き起こしている原因についてのアイデアはありますか?

4

9 に答える 9

24

@thomask

はい、ハイパーターミナルは実際に SetCommState の DCB で fAbortOnError を有効にします。これは、SerialPort オブジェクトによってスローされるほとんどの IOExceptions について説明しています。一部の PC / ハンドヘルドには、デフォルトでエラー フラグの中止がオンになっている UART もあります。そのため、シリアル ポートの init ルーチンでフラグをクリアすることが不可欠です (Microsoft はこれを怠っていました)。私は最近、これをより詳細に説明するために長い記事を書きました (興味がある場合は、これを参照してください)。

于 2010-07-05T01:32:39.393 に答える
4

ポートへの他人の接続を閉じることはできません。次のコードは機能しません。

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);
}
于 2009-01-14T01:33:25.187 に答える
2

信頼できる非同期通信を行う方法

ブロッキング メソッドを使用しないでください。内部ヘルパー クラスにはいくつかの微妙なバグがあります。

APM をセッション状態クラス、呼び出し間で共有されるバッファとバッファ カーソルを管理するインスタンス、EndReadおよびtry...catch. 通常の操作では、tryブロックが最後に行うべきことは、次のオーバーラップ I/O コールバックを への呼び出しでセットアップすることBeginRead()です。

うまくいかない場合catchは、restart メソッドへのデリゲートを非同期的に呼び出す必要があります。catch再起動ロジックが現在のセッションを破棄し (セッション状態はほぼ確実に破損しています)、新しいセッションを作成できるように、コールバックの実装はブロックの直後に終了する必要があります。セッション状態クラスに再起動メソッドを実装しないでください。これにより、セッションの破棄と再作成が妨げられるためです。

SerialPort オブジェクトが閉じられると (アプリケーションの終了時に発生します)、保留中の I/O 操作が存在する可能性があります。この場合、SerialPort を閉じるとコールバックがトリガーされ、これらの条件下EndReadでは、一般的な通信のたわごとと見分けがつかない例外がスローされます。ブロックで再起動動作を禁止するには、セッション状態にフラグを設定する必要がありcatchます。これにより、再起動方法が自然なシャットダウンに干渉するのを防ぎます。

このアーキテクチャは、SerialPort オブジェクトを予期せず保持しないという信頼性があります。

restart メソッドは、シリアル ポート オブジェクトのクローズと再オープンを管理します。オブジェクトを呼び出しClose()た後、オブジェクトを手放す機会を与えるために呼び出します。他の何かがポートをつかむ可能性があるため、ポートを再度開くときにこれに対処する準備をしてください.SerialPortThread.Sleep(5)

于 2009-01-28T06:46:53.357 に答える
2

アプリケーションでポートを開いたままにし、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 のセマンティクスに詳しくないので、これが機能するかどうかはわかりません。

于 2009-01-14T01:50:17.913 に答える
1

HyperTerminal がうまく動作しないという結論に達したと思います。次のテストを実行しました。

  1. 「コンソールモード」でサービスを開始すると、デバイスのオン/オフの切り替えが開始されます(LEDでわかります)。

  2. ハイパーターミナルを起動し、ポートに接続します。デバイスはオンのままです (HyperTerminal が DTR を発生させます) 私のサービスは、ポートを開くことができないことをイベント ログに書き込みます

  3. ハイパーターミナルを停止します。タスク マネージャーを使用して適切に閉じられていることを確認します

  4. デバイスはオフのまま (ハイパーターミナルが DTR を下げた)、アプリはイベント ログに書き込み続け、ポートを開くことができないと言います。

  5. 3 つ目のアプリケーション (共存させる必要があるアプリケーション) を起動し、ポートに接続するように指示します。私はそうします。ここにエラーはありません。

  6. 上記アプリを停止いたします。

  7. ほら、サービスが再び起動し、ポートが正常に開き、LED がオン/オフになります。

于 2009-01-14T15:14:11.940 に答える
1

このようにワークスレッドを変更しようとしましたが、まったく同じ結果になりました。ハイパーターミナルが「ポートのキャプチャ」に成功すると (スレッドがスリープしている間)、サービスはポートを再び開くことができなくなります。

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();
    }
}
于 2009-01-14T02:00:02.153 に答える
1

このコードは正しく動作しているようです。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();
            }
        }
    }
于 2009-01-14T02:13:55.680 に答える
0

サービスがポートを「所有」しないようにする正当な理由はありますか? 組み込みの UPS サービスを見てください。たとえば、COM1 に UPS が接続されていることを伝えると、そのポートに別れを告げることができます。ポートを共有するという強い運用上の要件がない限り、同じことをお勧めします。

于 2009-01-14T15:05:21.490 に答える
0

この答えはコメントになるまでに時間がかかりました...

プログラムが Thread.Sleep(1000) にあり、ハイパーターミナル接続を開くと、ハイパーターミナルがシリアル ポートを制御できると思います。その後、プログラムが起動してシリアル ポートを開こうとすると、IOException がスローされます。

メソッドを再設計し、別の方法でポートの開放を処理してみてください。

編集:プログラムが失敗したときにコンピューターを再起動する必要があることについて...

おそらく、プログラムが実際には閉じられていないため、タスクマネージャーを開いて、プログラム サービスが見つかるかどうかを確認してください。アプリケーションを終了する前に、必ずすべてのスレッドを停止してください。

于 2009-01-14T14:21:23.717 に答える