2014-04-29 4 views
0

제 질문이 너무 길면 미리 사과드립니다. "다른 클래스의 스레드에 의해 수신되는 메시지로 GUI의 데이터를 업데이트하는 방법"이라는 질문을 보았습니다.이 질문은 제가 수행하려고 시도한 것에 매우 가깝지만 답변이 도움이 될 정도로 자세하지 않았습니다.Worker Thread에서 Main (UI) 스레드로 전환 할 수 있습니까?

VB6 앱을 VB.NET (VS2013)으로 변환했습니다. 이 응용 프로그램의 주요 기능은 쿼리를 Linux 서버에 보내고 결과를 호출 양식에 표시하는 것입니다. WinSock 컨트롤은 더 이상 존재하지 않으므로 TcpClient 클래스와 관련된 함수를 처리하는 클래스를 만들었습니다. 성공적으로 서버에 연결하여 데이터를 보내고받을 수 있습니다.

문제는이 클래스를 사용하여 서버에 쿼리 메시지를 보내는 여러 가지 양식이 있다는 것입니다. 서버는 호출 양식에 표시 할 데이터로 응답합니다. 폼의 컨트롤을 업데이트하려고하면 "크로스 스레드 작업이 유효하지 않습니다 : 컨트롤 x가 만들어진 스레드 이외의 스레드에서 액세스되었습니다."라는 오류 메시지가 나타납니다. Control.InvokeRequired와 함께 Control.Invoke를 사용하여 Main/UI 스레드의 컨트롤을 업데이트해야하지만 VB에서 좋은 예제를 찾을 수 없습니다. 또한 각 폼에 다양한 컨트롤이있는 50 개 이상의 폼이 있으며 각 컨트롤에 대한 대리자 처리기를 쓰고 싶지 않습니다. 쓰레드와 델리게이트의 개념이 나에게 새롭다는 것도 언급해야합니다. 나는 지난 주 또는 2 주 동안 내가이 주제에서 찾을 수있는 모든 것을 읽었지 만, 나는 여전히 붙어있다!

메인 스레드로 다시 전환 할 수있는 방법이 있습니까? 그렇지 않다면 Control.Invoke를 사용하여 한 번에 여러 컨트롤을 처리 할 수있는 방법이 있습니까?

데이터를 보내고 받기 시작하기 전에 연결 직후 스레드를 시작했지만 netback.BeginRead는 콜백 함수가 실행되면 자체 스레드를 시작합니다. 또한 BeginRead 대신 Read를 사용해 보았습니다. 응답에 많은 양의 데이터가있는 경우 제대로 작동하지 않습니다. BeginRead는 더 잘 처리했습니다. Dorothy가 오즈에 머물러있는 것처럼 느껴진다. 나는 단지 메인 스레드에 집에 가고 싶다.

제공 할 수있는 도움에 미리 감사드립니다.

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

[비동기 프로그래밍] (http://msdn.microsoft.com/en-us/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-Accessing-Controls-from-Worker-Threads 코드가없는 경우 양식을 사용하면 해당 멤버에 액세스 할 수 없습니다. 이 경우'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 스레드의 기본 양식에 항상 액세스 할 수 있습니다. 안전하지 않은 실행합니다 Cross-thread exception 발생합니다 - 다음 샘플 코드에서

Friend Class Test 

    Public Event Message(text As String) 

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

End Class 

비동기 것을 알 수 있습니다.

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

Imports System.Threading 
Imports System.Threading.Tasks 
는 제안을하기 위해 시간이 걸렸습니다 사람들에게 감사드립니다. 해결책을 찾았습니다. 선호하는 솔루션이 아닐지라도, 그것은 아름답게 작동합니다. MSWINSCK.OCX를 툴바에 추가하고 COM/ActiveX 구성 요소로 사용합니다. AxMSWinsockLib.AxWinsock 컨트롤에는 DataArrival 이벤트가 포함되어 있으며 데이터가 도착하면 Main 스레드에 남아 있습니다.

가장 흥미로운 점은 AxMSWinsockLib.DMSWinsockControlEvents_DataArrivalEvent를 마우스 오른쪽 단추로 클릭하고 정의로 이동을 선택하면 개체 브라우저에 함수가 표시되고 비동기 읽기 및 BeginInvoke, EndInvoke 등을 처리하는 데 필요한 대리자가 처리됩니다. MicroSoft는 이미 시간을 할애하지 못했고, 스스로 경험할 수 없었던 어려운 작업을 수행 한 것처럼 보입니다!