之所以會出現這個錯誤的原因是開發 Windows 介面應用程式有個鐵則就是不應該跨執行緒去存取介面。如果這樣做的話很容易造成介面上資料的不一致/損毀,或者是其他不可預期的事情發生。
你所謂主執行緒的物件或變數應該是指表單介面上的物件吧。如果不是的話,你應該還是可以存取不會有錯誤,只不過資料的完整性要靠自己用程式碼控制。如果是的話,有幾個方式,第一個比較不安全,不建議使用,但很簡單(我範例都用 VB.NET 程式碼):
在表單的 Load 事件裡面加上
Form.CheckForIllegalCrossThreadCalls = False
他就會讓你跨執行緒處理,錯誤訊息不會出現,不過等於是違反了上述的原則,後果自負。
第二個方式是利用 Delegate (委派) 將存取 UI 的呼叫 marshal 到主執行緒,算是正統的做法,安全,但比較麻煩:先建立一個拿來更新 UI 的方法,如同下面的 UpdateUI,然後再建立一個有相同 signature 的委派,下面叫 UpdateUICallBack
Private Delegate Sub UpdateUICallBack(ByVal newText As String, ByVal c As Control)
Private Sub UpdateUI(ByVal newText As String, ByVal c As Control)
If Me.InvokeRequired() Then
Dim cb As New UpdateUICallBack(AddressOf UpdateUI)
Me.Invoke(cb, newText, c)
Else
c.Text = newText
End If
End Sub
之後需要改任何控制項的文字 (i.e Text 屬性) 就直接叫用 UpdateUI 就好了,跨執行緒存取也不會有問題,e.g:
UpdateUI("Updated from another thread", TextBox1)
第三個方式是使用 BackgroundWorker 元件,這是微軟因應多執行緒的需求提供的,也不錯用,不過他背後跟上面一樣也還是 marshal 到主執行緒:
把一個 BackgroundWorker 元件拉到你的表單上,把 WorkerReportsProgress 跟 WorkerSupportsCancellation 屬性都設成 True
再來建立這三個事件的 Event Handler
DoWork - 這是主要執行工作的地方,會在主執行緒以外的執行緒執行,不要在這裡更新 UI,需要的時候叫 BackgroundWorker 元件的 ReportProgress 就好
ProgressChanged - 當 ReportProgress 被呼叫的時候這個事件就會發生,這裡是由主執行緒在執行,可以任意存取 UI
RunWorkerCompleted - DoWord 結束後這個事件就會發生,也是由主執行緒在執行
最後只要叫 BackgroundWorker 的 RunWorkerAsync() 方法就會開始執行
有關 BackgroundWorker 的詳細資訊請看:
http://msdn.microsoft.com/zh-tw/library/system.componentmodel.backgroundworker.aspx
from:http://phorum.study-area.org/index.php?topic=51654.0
------------------sample code-------------
UpdateUI("StartTestFlow", textBox)
SetControlEnabled(commandBotton, False)
Private Delegate Sub UpdateUICallBack(ByVal newText As String, ByVal c As Control)
Private Delegate Sub SetControlEnabledCallBack(ByVal c As Control, ByVal value As Boolean)
Private Sub UpdateUI(ByVal newText As String, ByVal c As Control)
If Me.InvokeRequired() Then
Dim cb As New UpdateUICallBack(AddressOf UpdateUI)
Me.Invoke(cb, newText, c)
Else
c.Text = newText
End If
End Sub
Private Sub EnableUI(ByVal c As Control)
c.Enabled = True
End Sub
Private Sub SetControlEnabled(ByVal c As Control, ByVal value As Boolean)
If (Me.InvokeRequired) Then
Dim cb As New SetControlEnabledCallBack(AddressOf SetControlEnabled)
Me.Invoke(cb, New Object() {c, value})
Else
c.Enabled = value
End If
End Sub