Home [C# 筆記] Thread 執行緒(線程)
Post
Cancel

[C# 筆記] Thread 執行緒(線程)

Thread 執行緒(線程)

當點擊按鈕時,會去做很複雜的事(跑一萬次) 調試 > 輸出

不是真死,是假死

單Thread 執行緒的問題

模擬一個很複雜的方法,造成視窗卡死

當點擊按鈕時,會去做很複雜的事(跑一萬次)
調試 > 輸出

1
2
3
4
5
6
7
8
private void button1_Click(object sender, EventArgs e) {
    Test(); //模擬一個很複雜的方法(跑一萬次)
}
void Test() {
    for (int i = 0; i < 10000; i++) {
        Console.WriteLine(i);
    }
}

造成視窗卡死(不是真死,是假死),這就是單線程的問題。

在vs當中,每運行一個程序,cpu都會分配一個主線程來做,
我們能夠看得到操作視窗、移動、點擊…,都是主線程來做的,
當我們點擊這個按鈕時,主線程跑去做,就沒有人做原本的事,
就會造成視窗卡死(不是真死,是假死),這就是單線程的問題。

解決這個問題也很簡單,就多開一個線程給他。

解決視窗卡死問題(開新線程)

主線程幫我們去執行這個視窗
而新線程幫我們去執行Test這個方法(很複雜的事)

剛的問題就是,我只有一個線程,但他去做別的事了。 那我就再開一個線程去做複雜的事
讓主線程回歸做自己事

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void button1_Click(object sender, EventArgs e)
{
    //創建一個線程去執行這個方法
    Thread t = new Thread(Test); //傳入方法名

    //標記這個線程準備就緒了,可以隨時執行
    //具體什麼時候執行線程
    //由cpu決定
    t.Start();//strat不是真的開始,只是標記

    Test(); //模擬一個很複雜的方法(跑一萬次)
}
void Test() {
    for (int i = 0; i < 10000; i++) {
        Console.WriteLine(i);
    }
}

線程是cpu去決定執行的,我們不能控制決定的。
我們只能告訴cpu,這個線程準備好了,你隨時可以執行。

1
2
Thread t = new Thread(Test);  //創建一個線程去執行這個方法
t.Start();//strat不是真的開始,只是標記

但是,這個時候你就會發現,疑~~~

我應用程式都關了,怎麼程序怎麼還在跑(輸出Console)
(主線程都關了,程序還在跑…)

因為執行緒又分為:前景執行緒 和 背景執行緒

默認情況下,只要我們新增的執行緒,都叫做前景執行緒。

所以我們的應用程序必須要等到「前景執行緒」全部都執行完了,我才會徹底的結束掉關閉程序。
而「背景執行緒」是只要前景執行緒都關閉了,背景執行緒就會結束。

所以我們只要把剛新增的執行緒設為「背景執行緒」就可以了。

1
2
//線程設為後台線程
t.IsBackground = true;

前景執行緒 & 背景執行緒

  • 前景執行緒(前台線程) 前景執行緒都關閉了,程序才會關閉。

背景執行緒

  • 背景執行緒(後台線程) 只要前景執行緒都關閉了,背景執行緒就會馬上「自動關閉」。

默認情況下,只要我們新增的執行緒,都叫做前景執行緒。

所以會發生我關閉了應用程式,但程序卻還在跑(運行)
只要把剛設的thread設為背景執行緒就可以了

設定背景執行緒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 private void button1_Click(object sender, EventArgs e)
{
    //創建一個線程去執行這個方法
    Thread t = new Thread(Test); //傳入方法名

    //線程設為後台線程
    t.IsBackground = true;

    //標記這個線程準備就緒了,可以隨時執行
    //具體什麼時候執行線程
    //由cpu決定
    t.Start();//strat不是真的開始,只是標記, 我們只能告訴cpu這個thread準備好了,你隨時可以執行它

    Test(); //模擬一個很複雜的方法(跑一萬次)
}

void Test() {
    for (int i = 0; i < 10000; i++) {
        Console.WriteLine(i);
    }
}
1
2
3
4
Thread t = new Thread(Test); //產生新的thread指向Test方法
t.IsBackground = true; //設為背景thread,只要前景thread全都關閉了,背景thread馬上自動關閉
t.Start(); //這時候thread己經產生,但還沒運行,具體執行時間由CPU決定
Test(); //很複雜耗時的方法

在.net下,是不允許跨執行緒的訪問

報錯:System.InvalidOperationException: ‘跨執行緒作業無效: 存取控制項 ‘textBox1’ 時所使用的執行緒與建立控制項的執行緒不同。’

1
2
3
4
5
6
7
8
9
10
11
12
private void button1_Click(object sender, EventArgs e)
{
    Thread t = new Thread(Test); //創建一個線程去執行這個方法(傳入方法名)
    t.IsBackground = true;//線程設為後台線程
    t.Start();//strat不是真的開始,只是標記這個線程準備就緒了,可以隨時執行,具體執行時間由cpu決定
    Test();
}
void Test() {
    for (int i = 0; i < 100000; i++) {
        textBox1.Text = i.ToString(); //報錯,不允許跨執行緒的訪問
    }
}

怎麼辦?
取消跨線程的訪問

取消跨線程的訪問

在應用程式載入時,取消跨線程的訪問
Control.CheckForIllegalCrossThreadCalls = false;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void Form1_Load(object sender, EventArgs e) {
    //取消跨線程的訪問
    Control.CheckForIllegalCrossThreadCalls = false;
}
private void button1_Click(object sender, EventArgs e)
{
    Thread t = new Thread(Test); //創建一個線程去執行這個方法(傳入方法名)
    t.IsBackground = true;//線程設為後台線程
    t.Start();//strat不是真的開始,只是標記這個線程準備就緒了,可以隨時執行,具體執行時間由cpu決定
    Test();
}
void Test() {
    for (int i = 0; i < 100000; i++) {
        textBox1.Text = i.ToString(); //報錯,不允許跨執行緒的訪問
    }
}

thread.Abort 終止線程

這邊還會有個問題,在關閉應用程式時,有時候會出現異常,
異常不是每次都會出現,為什麼會這樣?
因為關閉應用程式時,
主線程沒了,視窗沒了,資源被釋放了,
但新線程可能某種原因,沒有馬上結束,
他卻還在訪問主線程的textBox1,就拋異常了

這時候就在關閉應用程式時,Abort()線程就可以了

1
2
3
4
5
6
7
8
9
10
11
12
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    //異常不是每次都會出現,關閉應用程式時
    //主線程沒了,視窗沒了,資源釋放了
    //但新線程可能某種原因,沒有馬上結束,
    //還在訪問主線程的textBox1就拋異常了

    //當你點擊關閉視窗的時候,判斷新線程是否為null
    if (t != null) {
        t.Abort(); //結束這線程 (已過時)
    }
}

要注意的是:線程被abort後,就不能再strat了

1
2
3
4
5
6
7
Thread t = new Thread(Test); 
t.IsBackground = true;
t.Start();

//線程被abort後,就不能再strat了
t.Abort(); 
t.Start(); //報錯

Thread.Sleep

讓線程停止運行3秒後,再執行”Hello”

1
2
Thread.Sleep(3000); //睡3秒
Console.WriteLine("Hello");

完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Thread t;
private void button1_Click(object sender, EventArgs e)
{
    //創建一個線程去執行這個方法
    t = new Thread(Test); //傳入方法名

    //線程設為後台線程
    t.IsBackground = true;

    //標記這個線程準備就緒了,可以隨時執行
    //具體什麼時候執行線程
    //由cpu決定
    t.Start();//strat不是真的開始,只是標記

    //線程是cpu去決定執行的,我們不能決定

    Test(); //模擬一個很複雜的方法(跑一萬次)

    //線程被abort後,就不能再strat了
    //t.Abort(); 
    //t.Start(); //報錯
    
}
void Test() {
    for (int i = 0; i < 100000; i++) {
        textBox1.Text = i.ToString();
    }
}

private void Form1_Load(object sender, EventArgs e)
{
    //取消跨線程的訪問
    Control.CheckForIllegalCrossThreadCalls = false;
}

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    //異常不是每次都會出現,關閉應用程式時
    //主線程沒了,視窗沒了,資源釋放了
    //但新線程可能某種原因,沒有馬上結束,
    //還在訪問主線程的textBox1就拋異常了


    //當你點擊關閉視窗的時候,判斷新線程是否為null
    if (t != null) {
        t.Abort(); //結束這線程 (已過時)
    }
}

總結

  • 單線程帶來程式假死的問題
  • 怎麼解決,用多線程,多開一個線程
  • 前台線程:所有的前台線程全部關閉了,這個程序才會關閉
  • 後台線程:所有的前台線程全部一關閉了,後台線程自動關閉,然後程序自動關閉
  • Thread.Start()啟動線程:是告訴CPU我可以被執行了,具體什麼時候執行,由CPU決定,我們是不能寫程式碼決定的,我們做不到的
  • Thread.Abort()終止線程,終止完成之後,不能再Start()
  • Thread.Sleep(1) 可以使當前線程停止一段時間再運行(睡一下)

多线程
system.threading.thread
[C#.NET][Thread] 背景執行緒與前景執行緒的差別

This post is licensed under CC BY 4.0 by the author.