Извиняюсь заранее, если мой вопрос слишком длинный. Я рассмотрел вопрос «Как обновить данные в графическом интерфейсе с сообщениями, которые принимаются потоком другого класса?», И это очень близко к тому, что я пытаюсь сделать, но ответ был недостаточно подробным, чтобы быть полезным.Есть ли возможность переключиться с рабочей резьбы на главную (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
Возможно, вы захотите изучить [Асинхронное программирование] (http://msdn.microsoft.com/en-us/library/hh191443.aspx). Nitpicking: 'On Error GoTo' должен быть преобразован в' Try ... Catch' и 'Dim myReadBuffer (BUFFER_SIZE), так как Byte' присваивает еще один элемент массива, который вам нужен (это должно быть' BUFFER_SIZE - 1'). –
Я решил перенести свой ответ на комментарий, потому что это была скорее ссылка, а не конкретная информация. Если код в форме, то да, используйте 'InvokeRequired' и' Invoke'. Ниже приведено полное объяснение того, как построить решение пошаговое: http://www.vbforums.com/showthread.php?498387-Accessing-Controls-from-Worker-Threads Если код отсутствует, форма, то у вас нет доступа к этим членам. В этом случае вы должны использовать класс 'SynchronizationContext'. Эта ссылка выше дает пример ее использования в более позднем сообщении. – jmcilhinney