0

私の質問が長すぎる場合は、事前にお詫び申し上げます。「別のクラスのスレッドによって受信されているメッセージで GUI のデータを更新する方法は?」という質問を見ました。それは私がやろうとしていることに非常に近いですが、答えは役立つほど詳細ではありませんでした.

VB6 アプリを VB.NET (VS2013) に変換しました。アプリの主な機能は、クエリを Linux サーバーに送信し、結果を呼び出しフォームに表示することです。WinSock コントロールはもう存在しないので、TcpClient クラスに関連付けられた関数を処理するクラスを作成しました。サーバーに正常に接続し、データを送受信できます。

問題は、このクラスを使用してクエリ メッセージをサーバーに送信するフォームが複数あることです。サーバーは、呼び出し元のフォームに表示されるデータで応答します。フォーム上のコントロールを更新しようとすると、「クロススレッド操作が無効です: コントロール x は、それが作成されたスレッド以外のスレッドからアクセスされました。」というエラーが表示されます。メイン/UI スレッドのコントロールを更新するために、Control.InvokeRequired を Control.Invoke と共に使用することになっていることはわかっていますが、VB で適切な完全な例を見つけることができません。また、各フォームにさまざまなコントロールを備えた 50 を超えるフォームがあります。各コントロールのデリゲート ハンドラーを作成したくありません。また、スレッドとデリゲートの概念は私にとって非常に新しいものです。過去 1 週間か 2 週間、このテーマについて見つけられるものはすべて読んでいますが、まだ立ち往生しています!

メインスレッドに戻す方法はありますか? そうでない場合、Control.Invoke を 1 回だけ使用して多数のコントロールをカバーする方法はありますか?

データの送受信を開始する前に、接続直後にスレッドを開始しようとしましたが、netStream.BeginRead は、コールバック関数が発火すると、独自のスレッドを開始します。また、BeginRead の代わりに Read を使用してみました。応答に大量のデータがある場合はうまく機能しませんでしたが、BeginRead の方がうまく処理できました。ドロシーがオズで立ち往生しているように感じます。メイン スレッドに戻りたいだけです。

ご協力いただきありがとうございます。

Option Explicit On
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading

Friend Class ATISTcpClient
Public Event Receive(ByVal data As String)
Private Shared WithEvents oRlogin As TcpClient
Private netStream As NetworkStream

Private BUFFER_SIZE As Integer = 8192
Private DataBuffer(BUFFER_SIZE) As Byte

Public Sub Connect()
    Try
    oRlogin = New Net.Sockets.TcpClient
    Dim localIP As IPAddress = IPAddress.Parse(myIPAddress)
    Dim localPrt As Int16 = myLocalPort
    Dim ipLocalEndPoint As New IPEndPoint(localIP, localPrt)

    oRlogin = New TcpClient(ipLocalEndPoint)
    oRlogin.NoDelay = True
    oRlogin.Connect(RemoteHost, RemotePort)

    Catch e As ArgumentNullException
        Debug.Print("ArgumentNullException: {0}", e)
    Catch e As Net.Sockets.SocketException
        Debug.Print("SocketException: {0}", e)
    End Try

    If oRlogin.Connected() Then
        netStream = oRlogin.GetStream
        If netStream.CanRead Then
            netStream.BeginRead(DataBuffer, 0, BUFFER_SIZE, _
AddressOf DataArrival, DataBuffer)
        End If

        Send(vbNullChar)
        Send(User & vbNullChar)
        Send(User & vbNullChar)
        Send(Term & vbNullChar)
    End If
End Sub
Public Sub Send(newData As String)

    On Error GoTo send_err
    If netStream.CanWrite Then
        Dim sendBytes As [Byte]() = Encoding.UTF8.GetBytes(newData)
        netStream.Write(sendBytes, 0, sendBytes.Length)
    End If
    Exit Sub
send_err:
    Debug.Print("Error in Send: " & Err.Number & " " & Err.Description)

End Sub
Private Sub DataArrival(ByVal dr As IAsyncResult)
'This is where it switches to a WorkerThread.  It never switches back!

    On Error GoTo dataArrival_err

    Dim myReadBuffer(BUFFER_SIZE) As Byte
    Dim myData As String = ""
    Dim numberOfBytesRead As Integer = 0

    numberOfBytesRead = netStream.EndRead(dr)
    myReadBuffer = DataBuffer
    myData = myData & Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead)

    Do While netStream.DataAvailable
        numberOfBytesRead = netStream.Read(myReadBuffer, 0, myReadBuffer.Length)
        myData = myData & Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead)
    Loop

 'Send data back to calling form
    RaiseEvent Receive(myData)

 'Start reading again in case we don‘t have the entire response yet
    If netStream.CanRead Then
        netStream.BeginRead(DataBuffer, 0,BUFFER_SIZE,AddressOf DataArrival,DataBuffer)
    End If

    Exit Sub
dataArrival_err:
    Debug.Print("Error in DataArrival: "  & err.Number & err.Description)

End Sub
4

2 に答える 2

0

デリゲートを使用する代わりに、匿名メソッドを使用できます。

単線:

uicontrol.Window.Invoke(Sub() ...)

マルチライン:

uicontrol.Window.Invoke(
    Sub()
        ...
    End Sub
)

呼び出す必要があるたびに UI コントロールを渡したくない場合は、カスタム アプリケーション起動オブジェクトを作成します。

Friend NotInheritable Class Program

    Private Sub New()
    End Sub

    Public Shared ReadOnly Property Window() As Form
        Get
            Return Program.m_window
        End Get
    End Property

    <STAThread()> _
    Friend Shared Sub Main()
        Application.EnableVisualStyles()
        Application.SetCompatibleTextRenderingDefault(False)
        Dim window As New Form1()
        Program.m_window = window
        Application.Run(window)
    End Sub

    Private Shared m_window As Form

End Class

これで、UI スレッドのメイン フォームにいつでもアクセスできるようになります。

Friend Class Test

    Public Event Message(text As String)

    Public Sub Run()
        Program.Window.Invoke(Sub() RaiseEvent Message("Hello!"))
    End Sub

End Class

次のサンプル コードでは、Asynchronous - Unsafeの実行でCross-thread exception.

Imports System.Threading
Imports System.Threading.Tasks

Public Class Form1

    Public Sub New()
        Me.InitializeComponent()
        Me.cbOptions = New ComboBox() With {.TabIndex = 0, .Dock = DockStyle.Top, .DropDownStyle = ComboBoxStyle.DropDownList} : Me.cbOptions.Items.AddRange({"Asynchronous", "Synchronous"}) : Me.cbOptions.SelectedItem = "Asynchronous"
        Me.btnRunSafe = New Button() With {.TabIndex = 1, .Dock = DockStyle.Top, .Text = "Run safe!", .Height = 30}
        Me.btnRunUnsafe = New Button() With {.TabIndex = 2, .Dock = DockStyle.Top, .Text = "Run unsafe!", .Height = 30}
        Me.tbOutput = New RichTextBox() With {.TabIndex = 3, .Dock = DockStyle.Fill}
        Me.Controls.AddRange({Me.tbOutput, Me.btnRunUnsafe, Me.btnRunSafe, Me.cbOptions})
        Me.testInstance = New Test()
    End Sub

    Private Sub _ButtonRunSafeClicked(s As Object, e As EventArgs) Handles btnRunSafe.Click
        Dim mode As String = CStr(Me.cbOptions.SelectedItem)
        If (mode = "Synchronous") Then
            Me.testInstance.RunSafe(mode)
        Else 'If (mode = "Asynchronous") Then
            Task.Factory.StartNew(Sub() Me.testInstance.RunSafe(mode))
        End If
    End Sub

    Private Sub _ButtonRunUnsafeClicked(s As Object, e As EventArgs) Handles btnRunUnsafe.Click
        Dim mode As String = CStr(Me.cbOptions.SelectedItem)
        If (mode = "Synchronous") Then
            Me.testInstance.RunUnsafe(mode)
        Else 'If (mode = "Asynchronous") Then
            Task.Factory.StartNew(Sub() Me.testInstance.RunUnsafe(mode))
        End If
    End Sub

    Private Sub TestMessageReceived(text As String) Handles testInstance.Message
        Me.tbOutput.Text = (text & Environment.NewLine & Me.tbOutput.Text)
    End Sub

    Private WithEvents btnRunSafe As Button
    Private WithEvents btnRunUnsafe As Button
    Private WithEvents tbOutput As RichTextBox
    Private WithEvents cbOptions As ComboBox
    Private WithEvents testInstance As Test

    Friend Class Test

        Public Event Message(text As String)

        Public Sub RunSafe(mode As String)

            'Do some work:
            Thread.Sleep(2000)

            'Notify any listeners:
            Program.Window.Invoke(Sub() RaiseEvent Message(String.Format("Safe ({0}) @ {1}", mode, Date.Now)))

        End Sub

        Public Sub RunUnsafe(mode As String)

            'Do some work:
            Thread.Sleep(2000)

            'Notify any listeners:
            RaiseEvent Message(String.Format("Unsafe ({0}) @ {1}", mode, Date.Now))

        End Sub

    End Class

End Class
于 2014-04-30T08:11:00.507 に答える
0

時間を割いて提案をしてくれた人々に感謝します。解決策を見つけました。好ましい解決策ではないかもしれませんが、うまく機能します。MSWINSCK.OCX をツールバーに追加し、それを COM/ActiveX コンポーネントとして使用しました。AxMSWinsockLib.AxWinsock コントロールには DataArrival イベントが含まれており、データが到着するとメイン スレッドにとどまります。

最も興味深いのは、AxMSWinsockLib.DMSWinsockControlEvents_DataArrivalEvent を右クリックして [定義へ移動] を選択すると、オブジェクト ブラウザーに、非同期読み取りを処理する関数とデリゲート サブルーチン、および BeginInvoke、EndInvoke などを処理するために必要なデリゲートが表示されることです。自分で理解する時間や経験がなかった難しいことをすでにやっていました!

于 2014-05-08T14:46:42.080 に答える