我有一个带有datagrids的表单,该表单绑定到Data的绑定列表,该列表由来自tcpip连接的常量数据流填充。 TCPIP连接位于应该循环的线程上,当它找到足够的数据时,创建类Data的实例并将其添加到BindingList(Data)。我收到一条错误,上面写着“跨线程操作无效:控制''从其创建的线程以外的线程访问。”。堆栈跟踪指向将新数据添加到(数据的)绑定列表
的行DataList.Insert(0, dataItem)
我觉得奇怪的是它继续正常运行并填充我的数据网格。我对多线程编程不是很熟悉,而且我的大部分代码都是异步的。我有一个数据锁和一些互斥(我刚学到的东西,但我不确定我是否正确使用)。我在某处读到使用事件会有所帮助,但如果不需要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 :从不需要从UI更新bindingList(of Data),因此更改数据的唯一线程应该是DataReader类中的mDatathread。
答案 0 :(得分: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。