1

私は現在、VB.net (.net 3.5) で記述されたソフトウェアを維持しています。このソフトウェアは、ネットワークを介して他の場所で実行されている別のソフトウェアと通信しますが、これは制御できません。私は WinForms ソフトウェアやベスト プラクティスにあまり詳しくありませんが、プログラムについて理解していることは次のとおりです。

ユーザーが UI を介して特定のアクションを実行すると、プログラムはホスト プロセスにメッセージを送信し、応答が返ってくるのを待ち、応答の結果を処理してから、ユーザーが続行できるようにします。クライアントとホストの間でこの会話が行われている間は、UI をロックする必要があります。ホストが受けているストレスの量によっては、この会話に最大 2 ~ 3 秒かかる場合があります。

このソフトウェアの作成者は、この通信が行われている間、while ループで UI スレッドをブロックしています。明らかに、これは良いアプローチではありません。Windows がプログラムがロックアップしたことを報告し、終了するオプションを与える可能性があるからです。

私の質問は、これを行うためのベストプラクティスは何ですか。winforms プログラムには、数百のボタンとコントロールを備えたいくつかのウィンドウがあり、通信中はすべてブロックする必要があります。通信 (および待機) は、何百もの異なるユーザーとプログラムとの対話の結果として行われます。では、通信中にプログラム全体が入力、ボタンの押下、キーボードなどを受け付けないようにするにはどうすればよいでしょうか?

これは、現在どのように機能しているかを示す簡略化されたコードです。私は、このプログラムの通信部分を大幅に書き直すことを恐れていません。

Private clientSocket As New System.Net.Sockets.TcpClient()
Private serverStream As NetworkStream
Private readThread As Thread = New Thread(AddressOf readLoop)



Public Sub Connect()
    'This function called to start the connection when program starts
    Try
        clientSocket.Connect(_Address, _Port)
        serverStream = clientSocket.GetStream()

        readThread.IsBackground = True
        readThreadActive = True
        readThread.Start()


    Catch ex As Exception
        RaiseEvent communicationsErrorEvent("CONNECT: " & ex.Message.ToString)
    End Try
End Sub

Public Sub WriterLogOn(ByVal UserName As String, ByVal Password As String)
    'This Sub is called from a button press on a WinForm

    Dim xmlString As String = "blah" 'generate xmlstring to send to host including username and encrypted password

    communicationsState = state_WriterLogOn 'store the state from an enum
    'each communication type has its own enum

    write(xmlString)

    While communicationsState = state_WriterLogOn
        Thread.Sleep(1)
    End While
End Sub


Private Sub write(ByVal writeString As String)
    Try
        Dim outStream As Byte() = System.Text.Encoding.ASCII.GetBytes(writeString & Chr(0))
        serverStream.Write(outStream, 0, outStream.Length)
        serverStream.Flush()
    Catch ex As Exception
        RaiseEvent communicationsErrorEvent("WRITE: " & ex.Message)
    End Try
End Sub


Private Sub readLoop()

    Try
        While (readThreadActive) 'always true unless changed in certain conditions

            Dim buffSize As Integer
            Dim inStream(10024) As Byte
            buffSize = clientSocket.ReceiveBufferSize
            Dim numberOfBytesRead As Integer = 0
            Dim returnDataBuilder As StringBuilder = New StringBuilder

            Do
                numberOfBytesRead = serverStream.Read(inStream, 0, inStream.Length)
                returnDataBuilder.AppendFormat("{0}", Encoding.ASCII.GetString(inStream, 0, numberOfBytesRead))
            Loop While serverStream.DataAvailable

            Dim returnData As String = returnDataBuilder.ToString()


            If (returnData.Length > 0) Then
                ParseMessage(returnData)
            End If

            Threading.Thread.Sleep(1)

        End While

    Catch ex As Exception
        RaiseEvent communicationsErrorEvent("READLOOP: " & ex.Message)
    End Try

End Sub


Private Sub ParseMessage(ByVal readdata As String)
    'This function parses the string received and does the appropriate things with it
    'And when appropriate it sets the state back to idle, allowing the UI thread to unblock
    communicationsState = state_Idle
End Sub

読み取りループは常に実行されている必要があります。これは、UI スレッドをブロックすることなく、バックグラウンドで処理する必要がある非送信請求メッセージをいつでも受信できるためです。(これは ParseMessage() サブから行われます)。

したがって、これが UI スレッドのブロックを処理する悪い方法であることは十分にわかっています。しかし、いろいろ調べてみたところ、別のスレッドで受信した他の通信によって解放されるまで、アプリケーション全体のすべてのユーザー入力を停止するための適切な解決策が見つかりませんでした。

さまざまな UI メソッドにコードを挿入して、状態をチェックし、ボタン/キーボードなどの動作を停止しようとしましたが、このコードを挿入する必要がある場所が非常に多く、間違った方法のように感じます。私は周りを検索しましたが、UI スレッドをブロックしない方法についての質問しか見つかりません。

UIスレッドでThreading.Sleepなしでこれをどのように処理できますか? モーダルフォームはこれを処理する方法ですか?

前もって感謝します!

4

1 に答える 1