本文講解如何使用多安全執行緒地使用.NET 中的Windows表單控制項。
使用多線程提高 Windows 表單應用程式的效能時,必須注意以安全執行緒方式調用控制項。
訪問 Windows 表單控制項本質上不是安全執行緒的。如果有兩個或多個線程操作某一控制項的狀態,則可能會迫使該控制項進入一種不一致的狀態。還可能出現其他與線程相關的 bug,包括爭用情況和死結。確保以安全執行緒方式訪問控制項非常重要。
.NET Framework 有助於在以非安全執行緒方式訪問控制項時檢測到這一問題。在調試器中運行應用程式時,如果建立某控制項的線程之外的其他線程試圖調用該控制項,則調試器會引發一個 InvalidOperationException,並提示訊息:“從不是建立控制項 control name 的線程訪問它。”
此異常在調試期間和運行時的某些情況下可靠地發生。強烈建議您在顯示此錯誤資訊時修複此問題。在調試以 .NET Framework 2.0 版之前的 .NET Framework 編寫的應用程式時,可能會出現此異常。 可以通過將 CheckForIllegalCrossThreadCalls 屬性的值設定為
false 來禁用此異常。這會使控制項以與在 Visual Studio 2003 下相同的方式運行。
對 Windows 表單控制項的非安全執行緒調用方式是從輔助線程直接調用。調用應用程式時,調試器會引發一個
InvalidOperationException,警告對控制項的調用不是安全執行緒的。該異常是在用普通多線程時會發生。
程式碼範例
private void setTextUnsafeBtn_Click(object sender, EventArgs e)
{
this.demoThread = new Thread(new ThreadStart(this.ThreadProcUnsafe));
this.demoThread.Start();
}
//線程不安全的方式
private void ThreadProcUnsafe()
{
this.textBox1.Text = "This text was set unsafely.";
}
對 Windows 表單控制項進行安全執行緒調用
查詢控制項的 InvokeRequired 屬性。
如果 InvokeRequired 返回 true,則使用實際調用控制項的委託來調用 Invoke。
如果 InvokeRequired 返回 false,則直接調用控制項。
在下面的程式碼範例中,此邏輯是在一個稱為 SetText 的工具 + 生產力方法中實現的。名為 SetTextDelegate 的委託類型封裝 SetText 方法。TextBox 控制項的 InvokeRequired 返回 true 時,SetText 方法建立 SetTextDelegate 的一個執行個體,並調用表單的 Invoke 方法。這使得 SetText 方法被建立 TextBox 控制項的線程調用,而且在此線程上下文中將直接設定 Text 屬性。
程式碼範例
private void setTextSafeBtn_Click( object sender, EventArgs e)
{
this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
}
//安全執行緒的方式
private void ThreadProcSafe()
{
this.SetText("This text was set safely.");
}
程式碼範例
private void SetText(stringtext)
{
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else{
this.textBox1.Text = text;
}
}
使用 BackgroundWorker 進行的安全執行緒調用
在應用程式中實現多線程的首選方式是使用 BackgroundWorker 組件。BackgroundWorker 組件使用事件驅動模型實現多線程。輔助線程運行 DoWork 事件處理常式,建立控制項的線程運行 ProgressChanged 和 RunWorkerCompleted 事件處理常式。注意不要從 DoWork 事件處理常式調用您的任何控制項。
下面的程式碼範例不非同步執行任何工作,因此沒有 DoWork 事件處理常式的實現。TextBox 控制項的 Text 屬性在 RunWorkerCompleted 事件處理常式中直接設定。
程式碼範例
private void setTextBackgroundWorkerBtn_Click( object sender, EventArgs e)
{
this.backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_RunWorkerCompleted(object sender,RunWorkerCompletedEventArgs e)
{
this.textBox1.Text = "This text was set safely by BackgroundWorker.";
}