2014-03-24 50 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。