2014-04-29 1 views
0

Извиняюсь заранее, если мой вопрос слишком длинный. Я рассмотрел вопрос «Как обновить данные в графическом интерфейсе с сообщениями, которые принимаются потоком другого класса?», И это очень близко к тому, что я пытаюсь сделать, но ответ был недостаточно подробным, чтобы быть полезным.Есть ли возможность переключиться с рабочей резьбы на главную (UI) резьбу?

Я конвертировал приложение VB6 в VB.NET (VS2013). Основная функция приложения - отправить запросы на сервер Linux и отобразить результаты в вызывающей форме. Поскольку элемент управления WinSock больше не существует, я создал класс для обработки функций, связанных с классом TcpClient. Я могу успешно подключиться к серверу и отправлять и получать данные.

Проблема заключается в том, что у меня есть несколько форм, которые используют этот класс для отправки сообщений запроса на сервер. Сервер отвечает данными, которые будут отображаться в вызывающей форме. Когда я пытаюсь обновить элемент управления в форме, я получаю сообщение об ошибке «Неверная операция кросс-потока: Control 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

Возможно, вы захотите изучить [Асинхронное программирование] (http://msdn.microsoft.com/en-us/library/hh191443.aspx). Nitpicking: 'On Error GoTo' должен быть преобразован в' Try ... Catch' и 'Dim myReadBuffer (BUFFER_SIZE), так как 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() ...) 

Multiline:

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

Если вы не хотите, чтобы передать пользовательский интерфейс элемента управления каждый раз, когда вам нужно вызвать, создать 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 

Теперь у вас всегда будет доступ к основной форме потока пользовательского интерфейса.

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 и выберите «Перейти к определению», браузер объектов отобразит функции и делегирует субтитры для обработки асинхронного чтения и необходимых делегатов для обработки BeginInvoke, EndInvoke и т. Д. Похоже, MicroSoft уже проделала трудную работу, в которой у меня не было времени или опыта, чтобы разобраться самостоятельно!

 Смежные вопросы

  • Нет связанных вопросов^_^