先看這樣的一個例子:
點擊"多線程訪問"按鈕標籤中文本"此標籤被另一個線程設定文本"會變為"Hello"!
代碼是這樣寫的:
/// <summary>
/// 設定標籤的文本
/// </summary>
private void SetLableText()
{
this.label1.Text = "Hello!";
}
/// <summary>
/// 設定標籤的按鈕事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
System.Threading.Thread setLabelTextThread = new System.Threading.Thread(new System.Threading.ThreadStart(this.SetLableText));
setLabelTextThread.Start();
}
按照想法,這個功能是完成了,運行.點擊按鈕,卻出現了異常:
分析:label標籤控制項是主線程建立的,不能直接從另一個線程訪問.可以這樣認為:不能跨線程直接存取控制項;
如何才能實現這個功能呢?
在.NET中,所有的控制項都是從System.Windows.Forms.Control類派生,Control類提供了一個Invoke()方法,用於在建立控制項的線程中訪問線程.它的定義如下:
public Object Invoke(Delegate method);
它的參數為一個委託,代表建立控制項的線程中要執行的方法.
可以利用這個方法來實現這個功能.
首先定義一個委託:
public delegate void setLabelTextDelegate();//定義一個setLabelTextDelegate()的委託
在定義一個委託變數:
private setLabelTextDelegate setLabelText;
在表單的建構函式中給這個委託變數初始化:
public Form1()
{
InitializeComponent();
this.setLabelText = this.SetLableText;//SetLableText為上面的"設定標籤的文本"的方法
}
然後在定義一個方法.方法裡使用Invoke
private void ThreadMethod()
{
this.label1.Invoke(this.setLabelText);//setLabelText為上面定義的委託變數
}
接著把按鈕事件裡的代碼修改一下:
/// <summary>
/// 設定標籤的按鈕事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
System.Threading.Thread setLabelTextThread = new System.Threading.Thread(new System.Threading.ThreadStart(this.SetLableText));//這個方法修改為ThreadMethod,即:
// System.Threading.Thread setLabelTextThread = new System.Threading.Thread(new System.Threading.ThreadStart(this.ThreadMethod));
setLabelTextThread.Start();
}
這個就OK了,運行.點擊:
功能實現:
不過"設定標籤的文本"的方法SetLableText()是沒有參數的,在很多情況下,我們寫的方法都是需要參數的,下面我就把這個例子改成有參數的,並示範如何傳遞參數:
首先:改造"設定標籤的文本"的方法SetLableText()變成有參數的:
/// <summary>
/// 設定標籤的文本
/// </summary>
private void SetLableText(string info)
{
this.label1.Text = info;
}
既然這個方法有參數了,與它對應的委託應該使用參數:
public delegate void setLabelTextDelegate(string infor);
定義的委託變數還是在建構函式中初始化,這個不用改變什麼:
public Form1()
{
InitializeComponent();
this.setLabelText = this.SetLableText;
}
既然使用了參數,那麼Invoke()這個方法應該會有重載的方法吧?
對Invoke()這個方法是有重載的,它的定義如下:
public Object Invoke(Delegate method,param Object [] args);
第二個參數是一個object的數組,就意味著,可以把需要傳遞的參數放到這個數組裡面來進行傳遞
對ThreadMethod()改造:
private void ThreadMethod(Object info)
{
this.label1.Invoke(this.setLabelText, new object[] { info});
}
注意紅色部分,為添加的參數
最後是按鈕事件的改造了:
/// <summary>
/// 設定標籤的按鈕事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
System.Threading.Thread setLabelTextThread = new System.Threading.Thread(this.ThreadMethod);
setLabelTextThread.Start("Hello!");
}
改造成功:
跨線程訪問控制項步驟可以總結一下:
(1)將訪問的控制項代碼封裝為一個方法;
(2)根據方法自訂一個對應委託;
(3)增加一個定義的委託類型的欄位,並把前面訪問控制項的方法"掛接"到此欄位中;
(4)編寫一個線程方法,在此方法中調用要訪問控制項的Invoke方法,並把定義好了的委託欄位做為參數傳入.
(5)在合適的地方建立線程並啟動運行
本人的第一篇博文.所寫的都是雕蟲小技,不足掛齒.只是希望把自己的心得分享出來.