2014-04-29 43 views
0

如果我的问题太冗长,我很抱歉。我看了一个问题:“如何使用另一个类的线程正在接收的消息来更新GUI中的数据?”,这与我正在尝试做的非常接近,但是答案不够详细,无法提供帮助。是否有从工作线程切换到主(UI)线程?

我已经将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 
+1

您可能想查看[Asynchronous Programming](http://msdn.microsoft.com/zh-cn/library/hh191443.aspx)。 Nitpicking:'On Error GoTo'应该转换为'Try ... Catch'和'Dim myReadBuffer(BUFFER_SIZE)As Byte'分配一个多于你想要的数组元素(它应该是'BUFFER_SIZE - 1')。 –

+1

我决定将我的答案提交给评论,因为它主要是一个链接而不是具体的信息。 如果代码在表单中,那么使用'InvokeRequired'和'Invoke'。以下是关于如何逐步构建解决方案的完整说明: http://www.vbforums.com/showthread.php?498387-访问 - 控制 - 从 - 工作线程 如果代码不在表单然后你无法访问这些成员。在这种情况下,你应该使用'SynchronizationContext'类。上面的链接提供了一个在以后的帖子中使用它的例子。 – jmcilhinney

回答

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 

在下面的示例代码,请注意异步 - 不安全运行将抛出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 
0

谢谢那些谁花时间来提出建议。我找到了解决方案。虽然它可能不是首选的解决方案,但它的运行非常好。我只是将MSWINSCK.OCX添加到我的工具栏中,并将其用作COM/ActiveX组件。 AxMSWinsockLib.AxWinsock控件包含一个DataArrival事件,当数据到达时它停留在主线程中。

最有趣的是,如果右键单击AxMSWinsockLib.DMSWinsockControlEvents_DataArrivalEvent并选择Go To Definition,对象浏览器将显示函数和委托subs以处理异步读取和处理BeginInvoke,EndInvoke等所需的委托。看起来MicroSoft已经完成了我没有足够的时间或经验自己搞清楚的难题!