System.InvalidOperationException: 跨執行緒作業無效: 存取控制項 'txtLog' 時所使用的執行緒與建立控制項的執行緒不同。
這個問題很明顯是在多執行緒的物件中,要在另一個執行緒物件裡做動作是會有問題的
我寫了一個小程式重現一下這個問題,
程式很簡單,畫面如下
會出現錯誤的程式碼如下:
Public Class Form3 Dim _testTimer As Timers.Timer Dim _timerInterval As Integer = 3 Dim _timerCount As Integer Private Sub btnStopTimer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStopTimer.Click ''停止時間 Me._testTimer.Stop() End Sub Private Sub btnStartTimer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStartTimer.Click ''重新開始時間計數 Me._testTimer.Start() End Sub Private Sub txtCmd_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtInterval.TextChanged ''重設時間常數 Me._timerInterval = CInt(txtInterval.Text) End Sub Private Sub Form3_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load txtInterval.Text = Me._timerInterval.ToString Me._testTimer = New Timers.Timer Me._testTimer.Interval = Me._timerInterval * 1000 AddHandler _testTimer.Elapsed, AddressOf Me.TimerOnElapsed Me._testTimer.Start() txtLog.Text = "Time is Start" End Sub #Region "Handle Timer Event" Private Sub TimerOnElapsed(ByVal sender As Object, ByVal e As Timers.ElapsedEventArgs) Try Me._testTimer.Stop() _timerCount = _timerCount + 1 ''錯誤發生在下面那行 txtLog.AppendText(Me._timerCount.ToString & vbCrLf) Catch ex As Exception MsgBox(ex.ToString) Finally Me._testTimer.Interval = Me._timerInterval * 1000 Me._testTimer.Start() End Try End Sub #End Region End Class
因為我要 Handle Timer 的 Elapsed 事件,
所以將事件另外 Handle
AddHandler _testTimer.Elapsed, AddressOf Me.TimerOnElapsed
但是要把計數值寫入 TextBox 中會出現"跨執行緒作業無效"的錯誤訊息,
所以多加入委派的 Sub 將寫入 Textbox 的事件重新導向
Public Class Form3 Dim _testTimer As Timers.Timer Dim _timerInterval As Integer = 3 Dim _timerCount As Integer ''委派加入 TextBox 字串的動作 Private Delegate Sub AppendTextBoxCallback(ByVal txtBox As TextBox, ByVal strValue As String) Private Sub AppendTextBox(ByVal txtBox As TextBox, ByVal strValue As String) If Me.InvokeRequired Then Me.Invoke(New AppendTextBoxCallback(AddressOf Me.AppendTextBox), New Object() {txtBox, strValue}) Else txtBox.AppendText(vbCrLf & strValue) End If End Sub Private Sub btnStopTimer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStopTimer.Click ''停止時間 Me._testTimer.Stop() End Sub Private Sub btnStartTimer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStartTimer.Click ''重新開始時間計數 Me._testTimer.Start() End Sub Private Sub txtCmd_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtInterval.TextChanged ''重設時間常數 Me._timerInterval = CInt(txtInterval.Text) End Sub Private Sub Form3_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load txtInterval.Text = Me._timerInterval.ToString Me._testTimer = New Timers.Timer Me._testTimer.Interval = Me._timerInterval * 1000 AddHandler _testTimer.Elapsed, AddressOf Me.TimerOnElapsed Me._testTimer.Start() txtLog.Text = "Time is Start" End Sub #Region "Handle Timer Event" Private Sub TimerOnElapsed(ByVal sender As Object, ByVal e As Timers.ElapsedEventArgs) Try Me._testTimer.Stop() _timerCount = _timerCount + 1 'txtLog.AppendText(Me._timerCount.ToString & vbCrLf) '交由委派的 Sub 去工作 AppendTextBox(txtLog, Me._timerCount.ToString) Catch ex As Exception MsgBox(ex.ToString) Finally Me._testTimer.Interval = Me._timerInterval * 1000 Me._testTimer.Start() End Try End Sub #End Region End Class
講了那麼多,主要就是要加入 Delegate 這行,
Delegate Sub AppendTextBoxCallback(ByVal txtBox As TextBox, ByVal strValue As String)
並在要做的動作寫在委派的 Sub 中就可以了。
Private Sub AppendTextBox(ByVal txtBox As TextBox, ByVal strValue As String) If Me.InvokeRequired Then Me.Invoke(New AppendTextBoxCallback(AddressOf Me.AppendTextBox), New Object() {txtBox, strValue}) Else ''想要做的事情寫在這裡 End If End Sub