私は現在、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なしでこれをどのように処理できますか? モーダルフォームはこれを処理する方法ですか?
前もって感謝します!