我已将VB6应用程序转换为VB.NET(VS2013)。该应用程序的主要功能是将查询发送到Linux服务器并在调用表单上显示结果。由于WinSock控件不再存在,我创建了一个类来处理与TcpClient类关联的函数。我可以成功连接到服务器并发送和接收数据。
问题是我有多个表单使用此类向服务器发送查询消息。服务器响应数据以显示在调用表单上。当我尝试更新窗体上的控件时,我收到错误“跨线程操作无效:控制x从其创建的线程以外的线程访问”。我知道我应该使用Control.InvokeRequired和Control.Invoke来更新Main / UI线程上的控件,但我在VB中找不到一个好的,完整的例子。另外,我有50多个表单,每个表单上都有各种控件,我真的不想为每个控件编写一个委托处理程序。我还要提一下,线程和委托的概念对我来说是非常新的。在过去一两周里,我一直在阅读关于这个主题的所有内容,但我仍然被卡住了!
有没有办法只需切换回主线程?如果没有,有没有办法可以使用Control.Invoke只需一次覆盖多个控件?
在我开始发送和接收数据之前,我尝试在连接之后启动一个线程,但是一旦回调函数触发,netStream.BeginRead就会启动自己的线程。我也尝试使用Read而不是BeginRead。如果响应中有大量数据,它就无法正常工作,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
答案 0 :(得分:0)
不使用委托,而是可以使用匿名方法。
SINGLELINE:
uicontrol.Window.Invoke(Sub() ...)
多:
uicontrol.Window.Invoke(
Sub()
...
End Sub
)
如果您不想在每次需要调用时传递UI控件,请创建custom application startup object。
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
答案 1 :(得分:0)
感谢那些花时间提出建议的人。我找到了解决方案。虽然它可能不是首选的解决方案,但它的工作效果非常好。我只是将MSWINSCK.OCX添加到我的工具栏,并将其用作COM / ActiveX组件。 AxMSWinsockLib.AxWinsock控件包含一个DataArrival事件,它在数据到达时保留在Main线程中。
最有趣的是,如果右键单击AxMSWinsockLib.DMSWinsockControlEvents_DataArrivalEvent并选择Go To Definition,对象浏览器会显示函数和委托subs来处理异步读取以及处理BeginInvoke,EndInvoke等所需的委托。看来MicroSoft已经完成了我没有时间或经验来解决的困难!