How to access objects from another thread...

I modified this example to output to a textbox on a windows form. So instead of Console.WriteLine I used Textbox1.AppendText. Unfortuantely this won't work being that async sockets work in a seperate thread and I get an exception stating the control "textbox1" cannot be accessed from a thread other than the one it was created on.

How do I take the data from the receive event and move it to another object created on another thread



Answer this question

How to access objects from another thread...

  • jack katz

    I'll post the code i use.

    In my global vars:

    Delegate Sub UpdateLogDelegate(ByVal toAdd As String, ByVal newLIne As Boolean, ByVal color As Color)
    Dim updateLogDel As UpdateLogDelegate = New UpdateLogDelegate(AddressOf UpdateLog)

    I have a function that traps all gui updates from another thread(it doesn't matter what control you call invoke on as long as it is on the main form):

    Public Sub UpdateGuiFromThread(ByVal method As String, ByVal msg As String, _
    ByVal boolValue As Boolean, ByVal color As Drawing.Color)

    'if form is not currently in closing sequence, update the GUI
    If inExit = False Then

    Select Case method
    Case "updateSendButtonDel"
    Me.btn_asynch_upload.Invoke(updateSendButtonDel, boolValue)
    Case "updateServerDel"
    Me.txt_server_name.Invoke(updateServerDel, msg)
    Case "updateLogDel"
    Me.txt_log.Invoke(updateLogDel, msg, boolValue, color)
    Case "updateFileDel"
    Me.txt_server_name.Invoke(updateFileDel, msg)
    Case "updateStatusDel"
    Me.txt_server_name.Invoke(updateStatusDel, msg)
    Case "refreshGuiDel"
    Me.txt_server_name.Invoke(refreshGuiDel)
    Case "updateProgressBarDel"
    Me.txt_server_name.Invoke(updateProgressBarDel, CInt(msg))
    Case "updateTrayIconDel"
    Me.txt_server_name.Invoke(updateTrayIconDel, msg)
    End Select
    End If
    End Sub

    Then the updateLog function:

    Private Sub UpdateLog(ByVal toAdd As String, ByVal newLine As Boolean, ByVal color As Drawing.Color)
    Me.txt_log.SelectionColor = color
    Me.txt_log.SelectedText = toAdd
    'Me.txt_log.AppendText(toAdd)
    If newLine = True Then
    Me.txt_log.AppendText(Environment.NewLine)
    End If
    ScrollToBottom()
    End Sub

    Then the call from another thread:

    UpdateGuiFromThread("updateLogDel", "Sending file " + fileToUpload.ToString, True, NormColor)

    This is not the most elegant code but it works for me.

    Good Luck,

    Lushdog


  • Kartik Subramani

    Thank you shakalama, that actually did the trick. Where I went wrong is in how i called Invoke. I was calling d.invoke so switching that to me.invoke worked out perfectly.


  • abhig

    Interesting, i tried shaka's technique a few month's ago and it didn't work for me. I'll try it out again, it would cut down alot on that garbage i posted above :)

    lushdog


  • mr.flx

    Ok, i tried that using an example i found online and it still is giving the same error.

    Here's the code that I currently have:

    Imports System
    Imports System.Threading
    Imports System.Net
    Imports System.Net.Sockets

    Public Class Form1
    Protected Delegate Sub mydelsub(ByVal msg As String)

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Dim sock As System.Net.Sockets.Socket
    sock = New System.Net.Sockets.Socket(Net.Sockets.AddressFamily.InterNetwork, Net.Sockets.SocketType.Stream, Net.Sockets.ProtocolType.Tcp)
    sock.Connect(New IPEndPoint(IPAddress.Parse("127.0.0.1"), 80))


    Dim request As String = "GET / HTTP/1.1" + Environment.NewLine
    request = request + "host: http://joelclipperton.us" + Environment.NewLine
    request = request + "connection: close" + Environment.NewLine + Environment.NewLine


    Dim buffer As Byte() = System.Text.Encoding.ASCII.GetBytes(request)

    Dim state As AsyncState = New AsyncState
    state.sock = sock
    state.buffer = buffer

    sock.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, New AsyncCallback(AddressOf SendCallback), state)
    End Sub


    Public Sub ReceiveCallback(ByVal ar As IAsyncResult)
    Dim del As mydelsub
    del = New mydelsub(AddressOf printtobox)

    Try
    Dim state As AsyncState = CType(ar.AsyncState, AsyncState)
    Dim count As Integer = state.sock.EndReceive(ar)

    If (count > 0) Then
    del.Invoke(System.Text.Encoding.ASCII.GetString(state.buffer, 0, count))
    state.sock.BeginReceive(state.buffer, 0, state.buffer.Length, SocketFlags.None, New AsyncCallback(AddressOf ReceiveCallback), state)
    Else
    del.Invoke(Environment.NewLine + "DONE: Socket was closed by the remote peer")
    End If

    Catch ex As Exception
    del = New mydelsub(AddressOf showerror)
    del.Invoke(ex.ToString)
    End Try
    End Sub

    Public Sub SendCallback(ByVal ar As IAsyncResult)
    Dim del As mydelsub
    del = New mydelsub(AddressOf printtobox)
    Try
    Dim state As AsyncState = CType(ar.AsyncState, AsyncState)
    state.sock.EndSend(ar)

    del.Invoke("Data Sent...")
    del.Invoke("Waiting to receive data")
    del.Invoke("")

    state.sock.BeginReceive(state.buffer, 0, state.buffer.Length, SocketFlags.None, New AsyncCallback(AddressOf ReceiveCallback), state)
    Catch ex As Exception
    del = New mydelsub(AddressOf showerror)
    del.Invoke(ex.ToString)
    End Try
    End Sub

    Protected Sub showerror(ByVal msg As String)
    MessageBox.Show(msg)
    End Sub

    Protected Sub printtobox(ByVal msg As String)
    T
    extbox1.AppendText(msg)
    End Sub
    End Class


    Public Class AsyncState
    Public sock As Socket
    Public buffer As Byte()
    End Class

    I know the code works because I can send the output of the receive buffer to a message box and it works just fine. Upon executing I get a System.InvalidOperationException: Corsss-thread operation not valid: Control 'Textbox1' accessed from a thread other than the thread it was created on.

    So using a delegate I'm still getting the same error. Is there something wrong with how i've implemented delegates


  • al kerr

    Anyone have any ideas


  • Mike Cating

    Look up delegates and .invoke in the MSDN. Basically you call a delegate that will marshal your function and makes sure that the update to the gui (.appendText) is done on the GUI thread.

    I'd write the code out for you but my eyes are burning right now.

    lushdog

  • SpiderX81

    hi,

    the short and best way is this to creat a delegate for a method that take string, and then creat a method that check if the invokerequired if its required it call the delegate and you reference this method in your code not the textbox, this code is not tested but you got the idea

    Delegate Sub settxtconn(ByVal text As String)

    Sub SetTxtConnStatus(ByVal txt As String)
     If Me.txtConnStatus.InvokeRequired Then
       Dim d As settxtconn = AddressOf SetTxtConnStatus
       Me.Invoke(d, New Object() {txt})
     Else
       Me.txtConnStatus.Text = txt
     End If
    End Sub

    hope this helps

     



  • How to access objects from another thread...