2014-03-24 46 views
1

我有一个数据网格绑定到数据绑定列表的表单,该数据绑定列表由来自tcpip连接的恒定数据流填充。 TCPIP连接位于一个应该持续循环的线程上,并且当它找到足够的数据时创建类Data的一个实例并将其添加到(Data的)BindingList。我得到一个错误,说“跨线程操作无效:控制”从一个线程访问,而不是它创建的线程。“堆栈跟踪点,增加了新的数据(数据)的的BindingList行线程安全问题试图跨线程数据绑定

DataList.Insert(0, dataItem) 

什么是奇怪,我是那么它不断去并正确填充我的数据网格。我对多线程编程不是很有经验,大部分代码都是异步的。我有一个datalock和一些互斥体(我刚学过的东西,但不知道我是否正确使用)。我在某处阅读使用事件会有所帮助,但如果NewData事件没有必要,我可以摆脱它。任何帮助使我的代码线程安全将不胜感激。

表格上的结合是这样的:

myDataGrid.datasource = myDataReader.DataList 

这里是类启动胎面和读取数据(抱歉,我知道这是一个很大的代码,但我不想留下点什么出来这可能是重要的):

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 
#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("PLC") + 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 Data(matches(i).Value)) 
       Next 
       RaiseEvent NewDataAvaiable(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 OnNewData(ByVal newData As BindingList(Of Data)) Handles Me.NewDataAvaiable 
    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 

UPDATE:对的BindingList(的数据)将永远不需要从UI更新,从而改变数据的唯一线程应该是mDatathread中的DataReader类。

+0

任何建议,甚至猜测,将不胜感激我需要得到这个去! – BinaryDuck

回答

0

调查InvokeRequired,Invoke,Delegate。

这里的例子,我有一个UDP服务器在后台线程(后台工作)在主线程中接收MessageEntries和Listview1。这是NET4.0

遗留WinForms项目是由后台线程调用以下:

Delegate Sub SetListViewCallback(ByVal NewMsg As MessageEntry) 

Private Sub addItemToListView(ByVal NewMsg As MessageEntry) 
    Dim item1 As New ListViewItem(NewMsg.Time.ToString(), 0) 
    item1.SubItems.Add(NewMsg.IP.ToString()) 
    If ListView1.InvokeRequired Then 
     Dim d As New SetListViewCallback(AddressOf addItemToListView) 
     Me.Invoke(d, New Object() {NewMsg}) 
    Else 
     ListView1.Items.Add(item1) 
    End If 
End Sub 

编辑:我只是检查这个方法有BindedList作为DataGridView的数据源,它仍然有效。唯一不同的是,条件if检查DataGridView对象是否需要Invoke,而在Else节中则是修改BindedList。