如果使用多執行緒來提高 Windows 表單應用程式的性能,則必須確保以執行緒安全方式調用控制項。

 

訪問 Windows 表單控制項本質上不是執行緒安全的。 如果有兩個或多個執行緒操作某一控制項的狀態,則可能會迫使該控制項進入一種不一致的狀態。 還可能會出現其他與執行緒相關的 Bug,例如爭用情況和鎖死。 確保以執行緒安全方式訪問控制項非常重要。

 

在未使用 Invoke 方法的情況下,從不是創建某個控制項的執行緒的其他執行緒調用該控制項是不安全的。 以下非執行緒安全的調用的示例。




// This event handler creates a thread that calls a
// Windows Forms control in an unsafe way.
private void setTextUnsafeBtn_Click(object sender, EventArgs e)
{
this.demoThread = new Thread(new ThreadStart(this.ThreadProcUnsafe));
this.demoThread.Start();
}

 

// This method is executed on the worker thread and makes
// an unsafe call on the TextBox control.
private void ThreadProcUnsafe()
{
this.textBox1.Text = "This text was set unsafely.";
}




.NET Framework 可説明您檢測以非執行緒安全方式訪問控制項這一問題。 在調試器中運行應用程式時,如果一個不是創建某個控制項的執行緒的其他執行緒調用該控制項,則調試器會引發一個 InvalidOperationException,並顯示以下消息:「從不是創建控制項控制項名稱 的執行緒訪問它。

 

此異常在調試期間和運行時的某些情況下可靠地發生。 在調試以 .NET Framework 2.0之前的 .NET Framework 編寫的應用程式時,可能會出現此異常。 我們強烈建議您在發現此問題時進行修復,但您可以通過將CheckForIllegalCrossThreadCalls 屬性設置為 false 來禁用它。 這會使控制項像在 Visual Studio .NET 2003 和 .NET Framework 1.1 中一樣運行。

 

對 Windows 表單控制項進行執行緒安全調用
1.查詢控制項的 InvokeRequired 屬性。
2.如果 InvokeRequired 返回 true,則使用實際調用控制項的委託來調用 Invoke。
3.如果 InvokeRequired 返回 false,則直接調用控制項。

 

在下面的代碼示例中,將在由後臺執行緒執行的 ThreadProcSafe 方法中實現執行緒安全調用。 如果 TextBox控制項的 InvokeRequired 返回 true,則 ThreadProcSafe 方法會創建 SetTextCallback 的一個實例,並將該實例傳遞給表單的 Invoke 方法。 這使得 SetText 方法被創建 TextBox 控制項的執行緒調用,而且在此執行緒上下文中將直接設置 Text 屬性。




// This event handler creates a thread that calls a
// Windows Forms control in a thread-safe way.
private void setTextSafeBtn_Click(object sender, EventArgs e)
{
this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
}

 

// This method is executed on the worker thread and makes
// a thread-safe call on the TextBox control.
private void ThreadProcSafe()
{
this.SetText("This text was set safely.");
}

 

// This method demonstrates a pattern for making thread-safe
// calls on a Windows Forms control.
//
// If the calling thread is different from the thread that
// created the TextBox control, this method creates a
// SetTextCallback and calls itself asynchronously using the Invoke method.
//
// If the calling thread is the same as the thread that created
// the TextBox control, the Text property is set directly.

 

private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
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 事件處理常式。 可以從 ProgressChanged 和 RunWorkerCompleted 事件處理常式調用控制項。

 

使用 BackgroundWorker 進行執行緒安全調用
1.創建一個方法,該方法用於執行您希望在後臺執行緒中完成的工作。 不要調用由此方法中的主執行緒創建的控制項。
2.創建一個方法,用於在後臺工作完成後報告結果。 在此方法中可以調用主執行緒創建的控制項。
3.將步驟 1 中創建的方法綁定到 BackgroundWorker 的實例的 DoWork 事件,並將步驟 2 中創建的方法綁定到同一實例的 RunWorkerCompleted 事件。
4.若要啟動後臺執行緒,請調用 BackgroundWorker 實例的 RunWorkerAsync 方法。

 

在下面的代碼示例中,DoWork 事件處理常式使用 Sleep 來類比需要花費一些時間完成的工作。 它不調用表單的 TextBox 控制項。 TextBox 控制項的 Text 屬性在 RunWorkerCompleted 事件處理常式中直接設置。




// This BackgroundWorker is used to demonstrate the
// preferred way of performing asynchronous operations.
private BackgroundWorker backgroundWorker1;

 

// This event handler starts the form's
// BackgroundWorker by calling RunWorkerAsync.
//
// The Text property of the TextBox control is set
// when the BackgroundWorker raises the RunWorkerCompleted
// event.
private void setTextBackgroundWorkerBtn_Click(object sender, EventArgs e)
{
this.backgroundWorker1.RunWorkerAsync();
}

 

// This event handler sets the Text property of the TextBox
// control. It is called on the thread that created the
// TextBox control, so the call is thread-safe.
//
// BackgroundWorker is the preferred way to perform asynchronous operations.

 

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.textBox1.Text = "This text was set safely by BackgroundWorker.";
}




也可以使用 ProgressChanged 事件來報告背景工作的進度。 有關包含該事件的示例,請參見BackgroundWorker。

 

雖然多執行緒處理最適於運行過程和類方法,它也可以用於表單和控制項。使用時,請注意以下幾點:
只要有可能,便僅在用來創建它的執行緒上執行控制項的各種方法。若須從另一線程調用控制項的方法,則必須使用 Invoke 來調用該方法。
不要使用 SyncLock 語句鎖定操作控制項或表單的執行緒。由於控制項和表單的方法有時回檔到調用過程,因此可能會因無意中創建了鎖死而終止運行(鎖死是指兩個執行緒都等待對方釋放鎖定,從而導致應用程式暫停的情況)。

 

參考:

 

HTTP://msdn.microsoft.com/zh-cn/library/ms171728(v=vs.100).aspx
創作者介紹

資訊園

shadow 發表在 痞客邦 PIXNET 留言(0) 人氣()