12

私は定期的にGSMモデムと通信して受信したSMSメッセージのリストを取得する.net 4.0コンソールアプリケーションを作成しました(これはUSBモデムですが、コードはシリアルポートドライバーを介して接続し、ATコマンドを送信します-ちなみにそれはSierra Wireless モデムを使用していますが、変更できず、最新のドライバーを使用しています)。何が起こるかというと、ある期間 (数時間、数日) が経過すると、機能しなくなります。ここにログのスニペットがあります...

2012-04-17 23:07:31 DEBUG Modem Check (108) - Executing AT command 'AT+CPMS="ME"'...
2012-04-17 23:07:31 DEBUG Modem Check (108) - Finished executing 'AT+CPMS="ME"'
2012-04-17 23:07:31 DEBUG Modem Check (108) - Detaching event handlers for 'COM13'
2012-04-17 23:07:31 DEBUG Modem Check (108) - Disposing the SerialPort for 'COM13'

これでログは終わりです - 少なくとももう 1 つのステートメントが表示されると予想されますが、これ以上はありません。関連するコードは次のとおりです。

internal T Execute()
{
    var modemPort = new SerialPort();
    T ret;

    try
    {
        modemPort.ErrorReceived += ModemPortErrorReceived;

        modemPort.PortName = _descriptor.PortName;
        modemPort.Handshake = Handshake.None;
        modemPort.DataBits = 8;
        modemPort.StopBits = StopBits.One;
        modemPort.Parity = Parity.None;
        modemPort.ReadTimeout = ReadTimeout;
        modemPort.WriteTimeout = WriteTimeout;
        modemPort.NewLine = "\r\n";
        modemPort.BaudRate = _descriptor.Baud;

        if (!modemPort.IsOpen)
        {
            modemPort.Open();
        }

        ret = _command.Execute(modemPort, _logger);

        _logger.Debug("Detaching event handlers for '{0}'",
                      _descriptor.PortName);

        modemPort.ErrorReceived -= ModemPortErrorReceived;

        _logger.Debug("Disposing the SerialPort for '{0}'",
                      _descriptor.PortName);
    }
    catch (IOException ex)
    {
        _logger.Error(ex.Message);

        throw new CommandException(
            string.Format(CultureInfo.CurrentCulture,
                          ModemWrapperStrings.COMMAND_ERROR,
                          ex.Message),
            ex);
    }
    catch (UnauthorizedAccessException ex)
    {
        _logger.Error(ex.Message);

        throw new CommandException(
            string.Format(CultureInfo.CurrentCulture,
                          ModemWrapperStrings.COMMAND_ERROR,
                          ex.Message),
            ex);
    }
    finally
    {
        modemPort.Dispose();

        _logger.Debug("Modem on port '{0}' disposed",
                      _descriptor.PortName);
    }

    return ret;
}

ご覧のとおり、SerialPort クラスの Dispose メソッドでハングします。

私はいくつかのグーグルを行って、私はこの問題に来ました: Serial Port Close Hangs the application from this thread: serial port hangs while closed . 別のスレッドでポートを閉じるのが賢明なようですが、それはフォーム アプリケーションのためだけですか? 私の場合、単純なコンソール アプリケーションを持っているので、適用されないと思います (メイン スレッドのループで実行されているだけです)。それが実際にこの問題なのかどうかもわかりません (モデムのシリアル ポート ドライバに問題がある可能性が高いと思いますが、わかりませんし、おそらくモデムに対して不当な扱いをしているのかもしれません)。私が見る限り、私には3つの選択肢があります。

  1. 別のスレッドでポートを閉じます
  2. ポートを閉じる前に遅延を入れる
  3. ポートを永久に開いたままにします

私はこれらの回避策のどれもあまり好きではありませんが、ポートを開いたままにして、何が起こるかを見ようと考えています (メモリリークやさらに悪いことに、モデムの他の問題が明らかになるような気がしますが、私はただ悲観的です)もしそうなら、おそらく24時間ごとに閉じて、もう一度開くことで逃げることができるでしょう)それで私の質問は...

この動作を引き起こしている可能性のあるこのコードには別の問題がありますか、それとも上記で概説したものに対する別の回避策はありますか?

4

2 に答える 2

16

SerialPort はデッドロックを起こしやすい傾向にあります。最も一般的な原因は、あなたが見つけたもので、DataReceived イベント ハンドラーで Invoke() を使用することによってトリガーされます。ここでは明らかにあなたのケースではありません。

これらのデッドロックは、SerialPort が背後で開始するワーカー スレッドに関連しています。そのスレッドは、ポートでの非同期イベントの検出に役立ちます。基になるネイティブ winapi は WaitCommEvent() です。そのワーカーは、DataReceived、PinChanged、および ErrorReceived イベントを機能させます。ErrorReceived の使用方法に注意してください

Dispose() メソッドは、Close() メソッドと同じことを行い、ワーカー スレッドに終了を通知します。ただし、スレッドが終了するのを待たないという欠点があります。これは、備考セクションの SerialPort.Close() に関する MSDN の記事に明示的に記載されている種類の問題のレシピです。

ポートがすぐに閉じられない可能性があるため、アプリケーションのベスト プラクティスは、Close メソッドを呼び出した後、Open メソッドを呼び出す前にしばらく待機することです。

率直に言って、これは「ベスト プラクティス」のアドバイスとして考えられる最悪のプラクティスです。正当な理由により、保証された安全な値はありません。1 ~ 2 秒待つだけで、99.9% は正常に動作します。0.1% の障害モードは、マシンの負荷が高く、ワーカー スレッドが時間内にクローズ状態を検出するのに十分なサイクルを取得できない場合に発生します。もちろん、完全にデバッグ不可能です。

この問題をパントします。プログラムの開始時にのみシリアルポートを開き、終了時に閉じてください。スレッド化の問題とは別に、これにより、別のプログラムが飛び込んでポートを盗んだときに、ポートへのアクセスがランダムに失われることもありません。また、ポートを閉じる必要がなくなった場合は、Windows が自動的に処理します。

于 2012-04-18T13:21:11.670 に答える
4

シリアル ポート オブジェクトから DataRecieved イベントまたはその他のイベントを使用している場合は、シリアル ポートを破棄する前にイベント ハンドラをオブジェクトから削除する必要があります。

mySerial.DataReceived -= DataReceivedHandler;
mySerial.Dispose();

破棄されたオブジェクトで発生するイベントがあるため、ハングが発生します...これは明らかにバグです。

ただし、あなたの場合はそれを行いました..ポートが閉じていないため、ハングが発生しています。おそらく、thread.sleep により、ポートを再オープンしようとする前に「解決」できる可能性があります。おそらくハードウェア固有のものでもあります...そのため、ベストプラクティスはありません。

フォーム コントロールと同じ: コントロール からすべてのイベント ハンドラーを削除する方法

于 2015-07-23T18:01:06.610 に答える