Click here to Skip to main content
15,884,298 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I have a program that is supposed to read from a number of TCPIP connections and display the parsed data on datagridviews. I created a class DataReader that creates the connection and launches a thread to read the data. I protected the bindinglist(of Data) (data is a class that will parse a string into different properties) with a mutex which I'm not sure I'm using right since it's a new concept to me and a synclock. When I start up the DataReader classes the datagridviews populate but then I get an error saying "Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.". The stack trace points to the line that adds new data to the bindinglist(of Data). Also if it matters the data can't be changed from the UI thread only populated from the DataReader. Any help would be greatly appreciated!

the databinding looks like this:
VB
myDataGridView1.datasource = myDataReader1.DataList


Here is the DataReader class(I know it's a lot of code but I didn't want to leave out something important):
VB
Public Class DataReader
    Implements INotifyPropertyChanged
#Region "Properties"
    Dim dataMutex As Mutex
    Dim UnparsedMutex As Mutex
    Dim mIP_Address As String
    Dim convertingData As Boolean = False
    Public Property IP_Address() As String
        Get
            Return mIP_Address
        End Get
        Set(ByVal value As String)
            mIP_Address = value
        End Set
    End Property
    Dim mPort As Integer
    Public Property Port As Integer
        Get
            Return mPort
        End Get
        Set(ByVal value As Integer)
            mPort = value
        End Set
    End Property
    Private WithEvents mDataList As BindingList(Of Data)
    Public ReadOnly Property DataList As BindingList(Of Data)
        Get
            Return mDataList
        End Get
    End Property
    Public Event valueChanged As Eventhandler
    Private Event NewDataAvaiable(ByVal newData As BindingList(Of Data))
    Private mUnparsedData As String = ""
    Private Property UnParsedData As String
        Get
            Return mUnparsedData
        End Get
        Set(ByVal value As String)
            mUnparsedData = value
        End Set
    End Property
#End Region
#Region "Private Variables"
    Dim mTCPIPClient As TcpClient
    Dim mConnected As Boolean
    Dim mTCPIPStream As NetworkStream
    Dim mLastError As String
    Dim mDataThread As System.Threading.Thread
    Private dataLock As New Object
    Private Delegate Sub AddNewDataDelegate(ByVal DataToAdd As BindingList(Of Data))
#End Region
#Region "Constructors"
    Public Sub New(ByVal IPAddress As String, ByVal Port As Integer)
        dataMutex = New Mutex(False, "MUTEXDATA")
        UnparsedMutex = New Mutex(False, "MUTEXUNPARSEDDATA")
        mIP_Address = IPAddress
        mPort = Port
        mConnected = False
        mDataList = New BindingList(Of Data)
        DataList.RaiseListChangedEvents = True
    End Sub
#End Region
#Region "Events"
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    Protected Sub OnPropertyChanged(ByVal name As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
    End Sub
#End Region
#Region "Methods"
    Public Sub ConnectTCPIP()
        Try
            If Not mTCPIPClient Is Nothing AndAlso mTCPIPClient.Connected Then
                mLastError = "Connection Error:Already connected"
                Exit Sub
            ElseIf String.IsNullOrEmpty(mIP_Address) Then
                mLastError = "Connection Error:No IP Address"
                Exit Sub
            End If
            DataList.Clear()
            UnParsedData = String.Empty

            mTCPIPClient = New TcpClient()
            mTCPIPClient.Connect(mIP_Address, mPort)
            If mTCPIPClient.Connected Then
                mTCPIPClient.ReceiveTimeout = 500
                mTCPIPClient.SendTimeout = 500
                mTCPIPClient.LingerState = New System.Net.Sockets.LingerOption(False, 0)
                mTCPIPClient.ReceiveBufferSize = 100000
                mTCPIPClient.SendBufferSize = 100000
                mTCPIPStream = mTCPIPClient.GetStream
                LaunchDataThread()
                If mDataThread.IsAlive Then mConnected = True
            Else
                mLastError = "Connection Error:Unknown Reason"
                Exit Sub
            End If
        Catch ex As Exception
            MessageBox.Show(ex.Message & ex.StackTrace)
        End Try
    End Sub
    Public Sub Disconnect()
        DisconnectTCPIP()
        KillDataThread()
    End Sub
    Private Sub DisconnectTCPIP()
        If Not mTCPIPClient Is Nothing AndAlso mTCPIPClient.Connected Then
            mTCPIPClient.Close()
            mTCPIPClient = Nothing
        End If
    End Sub
    Public Function IsConnected() As Boolean
        If mTCPIPClient Is Nothing OrElse mDataThread Is Nothing Then Return False
        Return mTCPIPClient.Connected AndAlso mDataThread.IsAlive
    End Function
    Public Sub LaunchDataThread()
        mDataThread = New System.Threading.Thread(AddressOf ReadData)
        mDataThread.Start()
    End Sub
    Public Sub KillDataThread()
        If Not mDataThread Is Nothing AndAlso mDataThread.IsAlive() Then
            mDataThread.Abort()
        End If
        mDataThread = Nothing
    End Sub
    Public Sub ReadData()
        Try
            While True
                Dim count As Short = 0
                While Not mTCPIPClient.Connected AndAlso count <= 5
                    System.Threading.Thread.Sleep(5000)
                    ConnectTCPIP()
                    count += 1
                End While

                If mTCPIPClient.Connected = False Then Exit Sub
                Dim dataBuffer(500) As Byte
                Dim readBytes As Integer = 0
                UnparsedMutex.WaitOne()
                Do While mTCPIPStream.DataAvailable
                    readBytes = mTCPIPStream.Read(dataBuffer, 0, 500)
                    UnParsedData += System.Text.Encoding.ASCII.GetString(dataBuffer.Take(readBytes).ToArray())
                Loop
                UnparsedMutex.ReleaseMutex()
                If UnParsedData.Length > 0 Then ProcessNewData()
                System.Threading.Thread.Sleep(500)
            End While

        Catch ex As Exception
            Windows.Forms.MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace)
        End Try
    End Sub
    Public Sub ProcessNewData()
        Dim dataToProcess As String = ""
        UnparsedMutex.WaitOne()
        If String.IsNullOrEmpty(UnParsedData) Then
            UnparsedMutex.ReleaseMutex()
            Exit Sub
        End If
        dataToProcess = UnParsedData.Substring(0, UnParsedData.LastIndexOf(CHR(3))
        UnParsedData = UnParsedData.Remove(0, dataToProcess.Length)
        UnparsedMutex.ReleaseMutex()
        If Not String.IsNullOrEmpty(dataToProcess) Then
            Dim matches = dataToProcess.Split(";"c)

            If matches.Count > 0 Then
                Dim newData As New BindingList(Of Data)
                Try
                    For i = 0 To matches.Count - 1
                        newData.Insert(0, New STS_Crane_Data(Name, matches(i).Value, Me.Format))
                    Next
                    Dim NewDataDel As New AddNewDataDelegate(AddressOf AddProcessedData)
                    NewDataDel.Invoke(newData)
                Catch ex As Exception
                    Windows.Forms.MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace)
                End Try
            End If
        End If
    End Sub
    Private Sub AddProcessedData(ByVal newData As BindingList(Of STS_Crane_Data))
        SyncLock dataLock
            dataMutex.WaitOne()
            For Each dataItem As Data In newData
                DataList.Insert(0, dataItem)
                If DataList.Count > 5000 Then DataList.RemoveAt(5000)
            Next
            dataMutex.ReleaseMutex()
        End SyncLock
    End Sub
#End Region
End Class
Posted
Updated 26-Mar-14 5:23am
v2
Comments
Sergey Alexandrovich Kryukov 25-Mar-14 11:52am    
I would advise to add one more tag: "System.Windows.Forms".
—SA

1 solution

You cannot call anything related to UI from non-UI thread. Instead, you need to use the method Invoke or BeginInvoke of System.Windows.Threading.Dispatcher (for both Forms or WPF) or System.Windows.Forms.Control (Forms only).

You will find detailed explanation of how it works and code samples in my past answers:
Control.Invoke() vs. Control.BeginInvoke()[^],
Problem with Treeview Scanner And MD5[^].

See also more references on threading:
How to get a keydown event to operate on a different thread in vb.net[^],
Control events not firing after enable disable + multithreading[^].

—SA
 
Share this answer
 
Comments
DBND 25-Mar-14 12:35pm    
I didn't think I was calling anything. The databinding takes place on a form with the datagridviews and the bindinglist manipulation happens in the DataReader class. I will look over your suggestions.
Sergey Alexandrovich Kryukov 25-Mar-14 12:41pm    
Don't worry, you do. You cannot do programming without a call. You should understand this term wider. If you, say, reading some property, or assigning value to some property, this is, due to the nature of properties, this is also a call.
—SA
Dave Kreskowiak 26-Mar-14 12:09pm    
Sure you are. You're calling a Set method on a property to set the DataSource of a UI control. You cannot TOUCH a UI control in any way at all from the non-UI thread.
DBND 25-Mar-14 13:57pm    
Perhaps I've just been staring at this stuff to long or that multithreadding really isn't my thing but I'm having problems seeing how to get this to work. Can I not use databinding across threads? Do I have to remove the databinding and notify the UI to update manually?
Sergey Alexandrovich Kryukov 25-Mar-14 14:27pm    
You can, but you have to delegate all the UI operations to UI thread; and I explain how.
If you already created some working code, you can fix it by moving UI-related part to UI thread invoked method(s).
—SA

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900