none
SerialPort.ReceivedBytesThresholdを更新できずに処理が停止する RRS feed

  • 質問

  • お世話になります。

    100msec周期で、シリアル通信を行うプログラムを動作させていたところ、

    極稀に件の処理中にて停止したままの現象が見受けられました。

    プログラムの動作として以下のようにしています。

    手順

    1. タイマで100msecを計測、イベント発生でフラグONするだけとし、イベント終了
    2. メインでフラグを監視し、フラグがONしていればシリアル通信の受信処理、送信処理を実行する
    3. 受信処理内で、受信したデータをcsvファイルに保存しています。
    4. 送信処理内で、毎回、SerialPort.ReceivedBytesThresholdを受信予定のByteサイズに直しながら送信

    上記の、手順4でエラーの発生もなく停止します。

    その他、関連がありそうな内容としては、

    • アプリケーションの再起動で通信可能
    • デバッグ機能の一時停止からのステップ実行でも同様の状態に陥る
    • 送信処理内で、最初にシリアルポートへアクセスする処理はSerialPort.ReceivedBytesThresholdである

    特定の条件科でSerialPortクラスにアクセスすると、

    処理を完了できずに無限ループに陥っているのかなと予想していますが、

    どういった状況で発生することが考えられますでしょうか。

    2016年7月6日 1:07

回答

  • とりあえず以下のコードで1万回以上回してみましたがReceivedBytesThresholdでとまることはありませんでした。

    Module Module1
    
        Sub Main()
            Dim rnd As New Random
            Dim bs(1000) As Byte
            Dim rcv(1000) As Byte
            Dim count As Integer
    
            Using portR = New System.IO.Ports.SerialPort("COM1", 115200)
                Using portS = New System.IO.Ports.SerialPort("COM2", 115200)
                    portS.Open()
                    portR.Open()
    
                    Dim ev As New System.Threading.ManualResetEvent(False)
    
                    AddHandler portR.DataReceived, Sub(s, e)
    
                                                       System.Diagnostics.Debug.Assert(portR.BytesToRead = portR.ReceivedBytesThreshold, "受信バイト数が想定外")
                                                       Dim rcount = portR.Read(rcv, 0, portR.ReceivedBytesThreshold)
                                                       System.Diagnostics.Debug.Assert(rcount = portR.ReceivedBytesThreshold, "バッファに残ってる")
                                                       ev.Set()
                                                   End Sub
    
                    Dim loopcount As Long = 0
                    Dim endtime As DateTime = DateTime.Now.AddHours(1)
                    Do While (endtime > DateTime.Now)
                        loopcount += 1
    
                        count = rnd.Next(1, 1000 - 1)
                        Console.WriteLine(String.Format("{0} {1,10:D} {2,4:D} {3}", DateTime.Now, loopcount, count, portR.BytesToRead))
    
                        portR.ReceivedBytesThreshold = count
                        portS.BaseStream.WriteAsync(bs, 0, count)
                        ev.WaitOne()
                        ev.Reset()
                    Loop
                End Using
            End Using
        End Sub
    
    End Module

    ReceivedBytesThresholdで止まっているとどのように判断したのでしょうか?
    ReceivedBytesThresholdの変更で止まるというのであればアプリケーションがフリーズして一切の操作ができなくなるはずですが、そのような状態になっているのでしょうか?その場合はDataReceivdイベント内で無限ループしている可能性のが高いと思います。
    フリーズするのではなく処理が進まないのであれば、ReceivedBytesThresholdで止まるのではなくDataReceivedイベントが発生していないので止まっているように見えるだけの可能性のが高いと思います。

    DataReceivedイベントは受信バッファにあるバイト数よりもReceivedBytesThresholdを小さくした時にも発生します。
    例えば受信バッファに1バイト以上残っている状態で、ReceivedBytesThresholdをバッファにあるバイト数以下に変更すると、DataReceiveイベントが即時発生しますが、受信処理を受け入れ状態にしていないために読み出しを失敗しているとかです。
    以下のコードではReceivedBytesThresholdで処理が止まっているように見えるでしょう。

    Imports System.IO.Ports
    Module Module1
        Sub Main(ByVal args As String())
            'COM3とCOM4を接続する
            Using portR As SerialPort = New SerialPort("COM3")
                Using portS As SerialPort = New SerialPort("COM4")
                    AddHandler portR.DataReceived, AddressOf Received
                    portR.Open()
                    portS.Open()
                    portR.ReceivedBytesThreshold = 100
                    portS.Write("Test")
    
                    '伝送が終わるのを適当にまつ
                    Do While (portR.BytesToRead <= 0)
                        System.Threading.Thread.Sleep(1)
                    Loop
    
                    Console.WriteLine("ReceivedBytesThresholdを変更します")
                    portR.ReceivedBytesThreshold = 1
                    Console.WriteLine("ReceivedBytesThresholdを変更しました")
                End Using
            End Using
        End Sub
    
        'DebuggerStepThroughでデバッガが止まったりステップ実行したりできない状態に
        <DebuggerStepThrough()> _
        Private Sub Received(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
            Console.WriteLine("DataReceivedイベントが発生しました")
            System.Threading.Thread.Sleep(100000)
        End Sub
    
    End Module
    

    あるいはEOF(&H1A)を受信したときにもDateReceivedイベントが発生しますが適切な判定をして処理していますか?
    受信は100%完璧に行えるものではなくノイズで文字化けしてEOFに化けた場合に想定しているよりも少ないバイト数でイベントが発生します。
    たとえばLF(&H0A)が1ビット化けるだけでEOF(&H1A)になってしまいます。
    少ない状態で発生したイベントでバッファから読み取っても、以降のバイトは受信バッファに残ったままになります。
    さらにその後にReceivedBytesThresholdをバッファにあるバイト数よりも少なく変更するとまた想定外のイベントが発生することになります。
    これが原因で止まっているかどうかは送信側でEOFを送信すると確認できるでしょう。

    あるいは、フレームエラーなどで一部が受信できなかった場合、受信バイト数は欠落した分だけ短くなるのでDataReceivedイベントは発生しなくなります。そうなるとイベント待ちをしたまま処理が進まなくなります。
    当然送信側が想定よりも短いバイト数しか送信してなければ同様です。
    PC側からのコマンドが相手側で受信エラーになって、エラー応答していると想定していないバイト数の応答をしてりるのかもしれませんし、ステップ実行していると相手側がタイムアウトしてエラー応答しているかもしれません。
    これで止まっているかどうかは送信側で意図的に短いバイト数しか送信しないとか、ケーブルを引っこ抜けば確認できるでしょう。

    このような可能性を考慮して見直しをしてみてはどうでしょうか。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2016年7月6日 12:06 止まって見えるコードを追加
    • 回答の候補に設定 星 睦美 2016年7月8日 0:17
    • 回答としてマーク K.Sekimoto 2016年7月22日 6:37
    2016年7月6日 9:45

すべての返信

  • とりあえず以下のコードで1万回以上回してみましたがReceivedBytesThresholdでとまることはありませんでした。

    Module Module1
    
        Sub Main()
            Dim rnd As New Random
            Dim bs(1000) As Byte
            Dim rcv(1000) As Byte
            Dim count As Integer
    
            Using portR = New System.IO.Ports.SerialPort("COM1", 115200)
                Using portS = New System.IO.Ports.SerialPort("COM2", 115200)
                    portS.Open()
                    portR.Open()
    
                    Dim ev As New System.Threading.ManualResetEvent(False)
    
                    AddHandler portR.DataReceived, Sub(s, e)
    
                                                       System.Diagnostics.Debug.Assert(portR.BytesToRead = portR.ReceivedBytesThreshold, "受信バイト数が想定外")
                                                       Dim rcount = portR.Read(rcv, 0, portR.ReceivedBytesThreshold)
                                                       System.Diagnostics.Debug.Assert(rcount = portR.ReceivedBytesThreshold, "バッファに残ってる")
                                                       ev.Set()
                                                   End Sub
    
                    Dim loopcount As Long = 0
                    Dim endtime As DateTime = DateTime.Now.AddHours(1)
                    Do While (endtime > DateTime.Now)
                        loopcount += 1
    
                        count = rnd.Next(1, 1000 - 1)
                        Console.WriteLine(String.Format("{0} {1,10:D} {2,4:D} {3}", DateTime.Now, loopcount, count, portR.BytesToRead))
    
                        portR.ReceivedBytesThreshold = count
                        portS.BaseStream.WriteAsync(bs, 0, count)
                        ev.WaitOne()
                        ev.Reset()
                    Loop
                End Using
            End Using
        End Sub
    
    End Module

    ReceivedBytesThresholdで止まっているとどのように判断したのでしょうか?
    ReceivedBytesThresholdの変更で止まるというのであればアプリケーションがフリーズして一切の操作ができなくなるはずですが、そのような状態になっているのでしょうか?その場合はDataReceivdイベント内で無限ループしている可能性のが高いと思います。
    フリーズするのではなく処理が進まないのであれば、ReceivedBytesThresholdで止まるのではなくDataReceivedイベントが発生していないので止まっているように見えるだけの可能性のが高いと思います。

    DataReceivedイベントは受信バッファにあるバイト数よりもReceivedBytesThresholdを小さくした時にも発生します。
    例えば受信バッファに1バイト以上残っている状態で、ReceivedBytesThresholdをバッファにあるバイト数以下に変更すると、DataReceiveイベントが即時発生しますが、受信処理を受け入れ状態にしていないために読み出しを失敗しているとかです。
    以下のコードではReceivedBytesThresholdで処理が止まっているように見えるでしょう。

    Imports System.IO.Ports
    Module Module1
        Sub Main(ByVal args As String())
            'COM3とCOM4を接続する
            Using portR As SerialPort = New SerialPort("COM3")
                Using portS As SerialPort = New SerialPort("COM4")
                    AddHandler portR.DataReceived, AddressOf Received
                    portR.Open()
                    portS.Open()
                    portR.ReceivedBytesThreshold = 100
                    portS.Write("Test")
    
                    '伝送が終わるのを適当にまつ
                    Do While (portR.BytesToRead <= 0)
                        System.Threading.Thread.Sleep(1)
                    Loop
    
                    Console.WriteLine("ReceivedBytesThresholdを変更します")
                    portR.ReceivedBytesThreshold = 1
                    Console.WriteLine("ReceivedBytesThresholdを変更しました")
                End Using
            End Using
        End Sub
    
        'DebuggerStepThroughでデバッガが止まったりステップ実行したりできない状態に
        <DebuggerStepThrough()> _
        Private Sub Received(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
            Console.WriteLine("DataReceivedイベントが発生しました")
            System.Threading.Thread.Sleep(100000)
        End Sub
    
    End Module
    

    あるいはEOF(&H1A)を受信したときにもDateReceivedイベントが発生しますが適切な判定をして処理していますか?
    受信は100%完璧に行えるものではなくノイズで文字化けしてEOFに化けた場合に想定しているよりも少ないバイト数でイベントが発生します。
    たとえばLF(&H0A)が1ビット化けるだけでEOF(&H1A)になってしまいます。
    少ない状態で発生したイベントでバッファから読み取っても、以降のバイトは受信バッファに残ったままになります。
    さらにその後にReceivedBytesThresholdをバッファにあるバイト数よりも少なく変更するとまた想定外のイベントが発生することになります。
    これが原因で止まっているかどうかは送信側でEOFを送信すると確認できるでしょう。

    あるいは、フレームエラーなどで一部が受信できなかった場合、受信バイト数は欠落した分だけ短くなるのでDataReceivedイベントは発生しなくなります。そうなるとイベント待ちをしたまま処理が進まなくなります。
    当然送信側が想定よりも短いバイト数しか送信してなければ同様です。
    PC側からのコマンドが相手側で受信エラーになって、エラー応答していると想定していないバイト数の応答をしてりるのかもしれませんし、ステップ実行していると相手側がタイムアウトしてエラー応答しているかもしれません。
    これで止まっているかどうかは送信側で意図的に短いバイト数しか送信しないとか、ケーブルを引っこ抜けば確認できるでしょう。

    このような可能性を考慮して見直しをしてみてはどうでしょうか。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2016年7月6日 12:06 止まって見えるコードを追加
    • 回答の候補に設定 星 睦美 2016年7月8日 0:17
    • 回答としてマーク K.Sekimoto 2016年7月22日 6:37
    2016年7月6日 9:45
  • gekka様 わざわざ検証までしていただき、ありがとうございます。

    >>ReceivedBytesThresholdで止まっているとどのように判断したのでしょうか?

    外部ツール(Analyzer)を接続し、通信状態をモニタしていたところ、急な停止を受けたのでデバッグ実行から一時停止を行いました。
    その際、停止していた行がReceivedBytesThresholdの変更を行う処理だったことから判断しています。

    >>ReceivedBytesThresholdの変更で止まるというのであればアプリケーションがフリーズして一切の操作ができなくなるはずですが、そのような状態になっているのでしょうか?その場合はDataReceivdイベント内で無限ループしている可能性のが高いと思います。

    再現率の低さから、確証を持って言えませんが、
    今回の状態に陥っても、
    フォーム上のボタン操作などが行えたり、デバッガの操作も行えてしまったので、
    フリーズという単語を避けました。
    ここについてはもう少し念入りに検証してみようと思います。



    >>フリーズするのではなく処理が進まないのであれば、ReceivedBytesThresholdで止まるのではなくDataReceivedイベントが発生していないので止まっているように見えるだけの可能性のが高いと思います。
    >>DataReceivedイベントは受信バッファにあるバイト数よりもReceivedBytesThresholdを小さくした時にも発生します。
    >>例えば受信バッファに1バイト以上残っている状態で、ReceivedBytesThresholdをバッファにあるバイト数以下に変更すると、DataReceiveイベントが即時発生しますが、受信処理を受け入れ状態にしていないために読み出しを失敗しているとかです。
    >>以下のコードではReceivedBytesThresholdで処理が止まっているように見えるでしょう。

    仰る通り、通信状態が常に正常であるとは限らないため、
    DataReceivedイベントを待つような処理にはしておりません。
    規定の時間で都度、受信結果をチェックし、
    受信に失敗していた場合は、バッファのクリアを行った上で送信のリトライをかけるようにしています。

    >>あるいはEOF(&H1A)を受信したときにもDateReceivedイベントが発生しますが適切な判定をして処理していますか?
    >>受信は100%完璧に行えるものではなくノイズで文字化けしてEOFに化けた場合に想定しているよりも少ないバイト数でイベントが発生します。
    >>たとえばLF(&H0A)が1ビット化けるだけでEOF(&H1A)になってしまいます。
    >>少ない状態で発生したイベントでバッファから読み取っても、以降のバイトは受信バッファに残ったままになります。
    >>さらにその後にReceivedBytesThresholdをバッファにあるバイト数よりも少なく変更するとまた想定外のイベントが発生することになります。
    >>これが原因で止まっているかどうかは送信側でEOFを送信すると確認できるでしょう。

    ・・・だから変なタイミングでイベントが発生していたんですね
    データ長やパラメータ等をやり取りしているため、
    &H1Aという数値はEofに限らずよく使用されます。
    そのため、ごり押しっぽい感じですが、相応の対策は行っております。

    >>あるいは、フレームエラーなどで一部が受信できなかった場合、受信バイト数は欠落した分だけ短くなるのでDataReceivedイベントは発生しなくなります。そうなるとイベント待ちをしたまま処理が進まなくなります。
    >>当然送信側が想定よりも短いバイト数しか送信してなければ同様です。
    >>PC側からのコマンドが相手側で受信エラーになって、エラー応答していると想定していないバイト数の応答をしてりるのかもしれませんし、ステップ実行していると相手側がタイムアウトしてエラー応答しているかもしれません。
    >>これで止まっているかどうかは送信側で意図的に短いバイト数しか送信しないとか、ケーブルを引っこ抜けば確認できるでしょう。

    ターゲットはマイコンで、データ長とチェックサムで判定しながら返信するようにプログラムしていますが、
    Analyzerのデータを見る限り、返信内容に問題は無さそうでした。
    送受信に失敗・・・というよりは、DataReceivedイベント中に他のイベントが割り込んでおかしくなっているのかな、
    と思い始めてきました。

    たくさんの案を出していただきありがとうございます。
    いくつか試してみたい内容もありましたので、
    引き続き、検証を進めていきたいと思います。
    2016年7月7日 0:55