要實現的效果:點擊按紐,窗口上的label上出現1~100數字的變化。
第一個實例(把窗口上的label上文字改成0):
using System;
using System.Windows.Forms;
namespace ThreadTest
{
public partial class Form1 : Form
{
public Form1(){InitializeComponent();}
private void Form1_Load(object sender, EventArgs e){label1.Text = "0";}
}
}
這個是最簡單的實例,很容易。
第二個實例(點擊button,循環顯示0動態變化到100數字):
using System;
using System.Windows.Forms;
namespace ThreadTest
{
public partial class Form1 : Form
{
public Form1(){InitializeComponent();}
private void Form1_Load(object sender, EventArgs e){label1.Text = "0";}
private void button1_Click(object sender, EventArgs e)
{
for(int i=0;i<101;i++){label1.Text = i.ToString();}
}
}
}
運行一下,點擊一下button1,沒有看到0~100動態變化,就直接到了100了。 原因:因為你的處理器速度太快了,就只能看到最后的結果。那么,怎樣才能看到中間過程呢?
我們先用函數的方式來實現上面的功能,寫個名為run的函數: private void run() { for(int i=0;i<101;i++){ label1.Text = i.ToString(); } }
這樣就可以直接調用run函數實現功能了,而不用在事件函數內寫代碼。
第三個實例(使用函數):
using System;
using System.Windows.Forms;
namespace ThreadTest
{
public partial class Form1 : Form
{
public Form1(){InitializeComponent();}
private void Form1_Load(object sender, EventArgs e){label1.Text = "0";}
private void run() { for(int i=0;i<101;i++){ label1.Text = i.ToString(); } }
private void button1_Click(object sender, EventArgs e){run();}
}
}
這里就需要在循環過程中加延時了,假定我們每隔1s的延時,lable1的值增加1,方法有很多。
首先我們用一個timer來實現延時。 添加一個timer, 命名為timer1,在timer1的tick事件內添加語句,改變label1的值。(Tick事件是每經過指定時間間隔后被觸發)。
第四個實例(使用timer),實現每隔1s的延時,lable1的值增加1,以達到動態變化的效果:
using System;
using System.Windows.Forms;
namespace ThreadTest
{
public partial class Form1 : Form
{
int i; //全局變量
public Form1(){InitializeComponent();}
private void Form1_Load(object sender, EventArgs e){label1.Text = "0";}
private void run()
{
i = 0;
timer1.Interval = 1000; //設置timer1的間隔時間
timer1.Start(); //啟動timer1
}
private void timer1_Tick(object sender, EventArgs e)
{
i++;
if (i > 100) {timer1.Stop();}
label1.Text = i.ToString();
}
private void button1_Click(object sender, EventArgs e) {run();}
}
}
我們運行一下,能夠看到0~100循環的過程了。
以上是我們平常的做法,讓label動態變化的效果,下面我們開始使用線程來實現上面的功能。
由於要使用多線程,我們需要引用
using System.Threading;
通過下面的語句就定義一個名為thread1的線程 private Thread thread1;
和定義函數極為相似,定義線程之后,就要進行實例化: thread1 = new Thread(new ThreadStart(run));
這個語句的意思就是實例化thread1並將run函數設定為thread1的入口函數(大概意思就是,讓run函數在線程thread1上執行)。
創建線程就算完成了,那么怎么運行線程呢? 其實和啟動timer1是類似的,thread1.Start();就運行了我們創建的線程thread1。
既然我們創建了線程,那么在關閉窗口的時候,就要撤消線程。添加FormClosing事件,在事件內部寫如撤消線程的代碼:
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (thread1.IsAlive) //判斷thread1是否存在,不能撤消一個不存在的線程,否則會引發異常
{ thread1.Abort(); //撤消thread1 }
}
這樣才算大功告成,整理的代碼如下:(在第三個實例的基礎上加以改動)。
第五個實例:
using System;
using System.Threading;
using System.Windows.Forms;
namespace ThreadTest
{
public partial class Form1 : Form
{
private Thread thread1;
public Form1(){InitializeComponent();}
private void Form1_Load(object sender, EventArgs e){label1.Text = "0";}
private void button1_Click(object sender, EventArgs e){thread1 = new Thread(new ThreadStart(run));thread1.Start();}
private void run(){for (int i = 0; i < 101; i++){label1.Text = i.ToString();}}
private void Form1_FormClosing(object sender, FormClosingEventArgs e){if (thread1.IsAlive){thread1.Abort();}}
}
}
運行一下,按button1,出錯了,怎么回事呢?
看看出錯原因,是在run函數內的label1.Text = i.ToString();語句上出的錯,沒錯啊,語法正確啊。解釋一下,出錯的原因是
為了保護數據的安全所以不能跨線程調用控件,而label1.Text = i.ToString();句則是在線程thread1上面調用主線程的控件,肯定會出錯的!怎么辦呢?用
委托啊,我的理解就是,線程thread1不能調用主線程的lable1,所以,就委托主線程來改變lable1的值。 首先看一個例子:(從例3改寫)(並不創建線程,僅有主線程) 創建一個函數,用來設置lable1的值;
private void set_lableText(string s) { label1.Text = s; } 當需要改變lable1的值時,就調用它,並傳遞要改變的值。
第六個實例:
using System;
using System.Windows.Forms;
namespace ThreadTest
{
public partial class Form1 : Form
{
public Form1(){InitializeComponent();}
private void Form1_Load(object sender, EventArgs e){label1.Text = "0";}
private void button1_Click(object sender, EventArgs e){run(); //調用run函數}
private void run(){for(int i=0;i<101;i++){set_lableText( i.ToString() );}}
private void set_lableText(string s){label1.Text = s;}
}
}
實現的功能與第三個實例是一樣的,只是,增加了一個函數。
到這里,需要了解一下委托這個東西,我們就需要委托主線程調用函數set_lableText(string s);來改變lable1的值。
首先聲明一個委托: delegate void set_Text(string s);
創建一個全局委托變量: set_Text Set_Text;
進行實例化: Set_Text = new set_Text(set_lableText); //括號內的set_lableText是委托要調用的函數(也就是例6寫的set_lableText(string s);函數)
現在,就剩下調用委托了,怎么調用委托呢?很簡單。 同過Invoke來調用,語句如下:
label1.Invoke(Set_Text, new object[] { i.ToString() }); //Set_Text是調用的委托,object[]則是我們要傳遞的參數
整理代碼如下,第七個實例:
using System;
using System.Threading;
using System.Windows.Forms;
namespace ThreadTest
{
public partial class Form1 : Form
{
private Thread thread1; //定義線程
delegate void set_Text(string s); //定義委托
set_Text Set_Text; //定義委托變更
public Form1(){InitializeComponent();}
private void Form1_Load(object sender, EventArgs e){label1.Text = "0"; Set_Text = new set_Text(set_lableText); //實例化}
private void button1_Click(object sender, EventArgs e){thread1 = new Thread(new ThreadStart(run)); thread1.Start();}
private void set_lableText(string s){label1.Text = s;}//主線程調用的函數
private void run()
{
for (int i = 0; i < 101; i++)
{
label1.Invoke(Set_Text, new object[] { i.ToString() }); //通過調用委托,來改變lable1的值
Thread.Sleep(1000); //線程休眠時間,單位是ms
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (thread1.IsAlive) //判斷thread1是否存在,不能撤消一個不存在的線程,否則會引發異常
{thread1.Abort(); //撤消thread1}
}
}
}
這樣,一個簡單的多線程程序就算完成了。