Home [C# 筆記] Thread 執行緒
Post
Cancel

[C# 筆記] Thread 執行緒

Thread 執行緒

執行無參數方法

1
2
3
4
5
6
7
8
9
10
11
static void Test()
{
    Console.WriteLine("Start");
    Thread.Sleep(1000);
    Console.WriteLine("Completed");
}
static void Main(string[] args)
{
    Thread t = new Thread(Test);
    t.Start();
}

使用匿名方法

1
2
3
4
5
static void Main(string[] args)
{
    Thread t = new Thread(() => Console.WriteLine($"Child Thread: {Thread.CurrentThread.ManagedThreadId}"));
    t.Start();
}

委派寫法:Thread t = new Thread(delegate () { Console.WriteLine($”Child Thread: {Thread.CurrentThread.ManagedThreadId}”); });

執行有參數方法

方法的參數設object,在Start中傳入

1
2
3
4
5
6
7
8
9
static void Download(object o) { 
    string str = o as string; //object轉換為string
    Console.WriteLine(str);
}
static void Main(string[] args)
{
    Thread t = new Thread(Download);
    t.Start(@"http://www.xxx.com/xx/xx/xx.mp4");
}

方法的參數設object (object為結構體Struct)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public struct Data {
    public string name;
    public string age;
}
static void Download(object o)
{
    Data data = (Data)o;
    Console.WriteLine(data.name);
}
static void Main(string[] args)
{
    Data data = new Data();
    data.name = "Rii";
    Thread t = new Thread(Download);
    t.Start(data);
}

傳遞數據第二種方法-自定義class

1
2
3
4
5
6
7
8
9
10
11
12
internal class DownloadTool
{
    public string URL { get; set; }
    public String Message { get; set; }
    public DownloadTool(string url, string message) {
        this.URL = url;
        this.Message = message;
    }
    public void Download() {
        Console.WriteLine($"從{URL}下載中…");
    }
}
1
2
3
4
5
6
static void Main(string[] args)
{
    var downloadTool = new DownloadTool("http://xx/xx", "test");
    Thread t = new Thread(downloadTool.Download);
    t.Start();
}

前景執行緒、背景執行緒

1
2
3
program > process > Thread
程式 > 進程 > 線程
寫好的Code > 被執行的Code > 執行緒
1
2
IsBackground = false; //前景執行緒(系統默認是前景Thread)
IsBackground = true; //背景執行緒
1
2
前景執行緒(前台線程):在主線程運行結束後,若前台線程沒有運行完則會阻止主線程的關閉
背景執行緒(後台線程):在主線程運行結束後,整個線程會結束

前景執行緒

運行後,會發現,Main()主執行緒運行結束後,若前景執行緒沒有運行完,則阻止主線程的關閉,會等待前景執行緒執行完任務為止。

1
2
3
4
5
6
7
8
9
10
11
static void Main(string[] args) //主執行緒(Main Thread)(主線程)
{
    Thread t = new Thread(Thread1) { IsBackground = false }; //false前景執行緒(子線程)
    t.Start();
    Console.WriteLine("Thread ending now...");
}
static void Thread1() {
    Console.WriteLine($"Thread + {Thread.CurrentThread.ManagedThreadId} Started...");
    Thread.Sleep(3000);
    Console.WriteLine($"Thread + {Thread.CurrentThread.ManagedThreadId} End...");
} 

輸出:

1
2
3
Thread ending now...
Thread + 5 Started...
Thread + 5 End...

前景執行緒必須全部執行完。
即使整個應用程式被關閉,程序(Process)仍然存在,前景執行緒不會結束。

背景執行緒

Main()主執行緒運行結束後,整個執行緒會結束。

1
2
3
4
5
6
7
8
9
10
11
static void Main(string[] args) //主執行緒(Main Thread)(主線程)
{
    Thread t = new Thread(Thread1) { IsBackground = true }; //true背景執行緒(子線程)
    t.Start();
    Console.WriteLine("Thread ending now...");
}
static void Thread1() {
    Console.WriteLine($"Thread + {Thread.CurrentThread.ManagedThreadId} Started...");
    Thread.Sleep(3000);
    Console.WriteLine($"Thread + {Thread.CurrentThread.ManagedThreadId} End...");
}

輸出:

1
2
Thread ending now...
Thread + 5 Started...

如果整個應用程式被關閉,所有背景執行緒也會結束。

執行緒的優先權 ThreadPriority

  • CPU在同一個時間只能做一件事,如果有多執行緒時,CPU會自動分配時間片段,同步交替進行的,讓你感覺是同時進行。
  • 優先權高,不代表優先你,而是指搶到CPU的時間比較多。可以擁有更多的時間
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static void A() {
    while (true) {
        Console.Write("A");
    }
}
static void B() {
    while (true) {
        Console.Write("B");
    }
}
static void Main(string[] args)
{
    Thread a = new Thread(A);
    Thread b = new Thread(B);

    a.Priority = ThreadPriority.Highest; //設a為最高優先級
    b.Priority = ThreadPriority.Lowest;

    a.Start();//此時還是unstart狀態,還不是running狀態
    b.Start();
}

執行緒池 ThreadPool

適合當有很多的小任務時,就可以放進ThreadPool佇列裡
ThreadPool.QueueUserWorkItem()
這樣可以減少因頻繁建立/釋放 Thread而耗費的時間。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void Main(string[] args)
{
    for (int i = 0; i < 10; i++) {
        ThreadPool.QueueUserWorkItem(Download);//ThreadPool是背景執行緒
    }
    Thread.Sleep(5000); //ThreadPool是背景執行緒,所以還沒執行就關閉,所以讓thread睡5秒
}
//模擬下載
static void Download(object state) {
    for (int i = 0; i < 3; i++)
    {
        Console.WriteLine($"Download... {Thread.CurrentThread.ManagedThreadId}");
        Thread.Sleep(100);
    }
}

Task

啟動Task的二種方式:

1. TaskFactory

1
2
TaskFactory tf = new TaskFactory(); //背景執行緒緒
tf.StartNew(Test);

2. Task

1
2
Task t = new Task(Test);
t.Start();

Task 連續任務 ContinueWith

  • 在一個任結束時,可以啟動多個任務
  • 連續任務, 也可以有連續任務
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void FirstDownload() {
    Console.WriteLine("Downloading...");
    Thread.Sleep(2000);
}
static void SecondAlert(Task t) {
    Console.WriteLine("XX下載完成…");
}
static void Main(string[] args)
{
    //TaskFactory tf = new TaskFactory(); //背景執行緒緒
    //tf.StartNew(Test);

    //Task t = new Task(Test);
    //t.Start();
    
    Task t1 = new Task(FirstDownload);
    Task t2 = t1.ContinueWith(SecondAlert); //t1執行完後會立即啟動
    Task t3 = t2.ContinueWith(SecondAlert); //連續任務t2, 也可以有連續任務
    t1.Start();

    Thread.Sleep(5000);
}

資源訪問衝突

當多個任務同時訪問和修改同一個文件或是數據的時候(跟讀寫有關的),由於多個任務同時修改文件和數據,就有可能導致數據出現不可預測的異常錯誤:

為解決這問題,可以通過使用lock解決資源衝突的問題

1
2
private object _lock = new object();
lock (_lock){  //要鎖定的Code }
1
2
3
4
5
6
7
8
9
private object _lock = new object();
public void Test() 
{
    //dosomething
    lock (_lock) {
        //要鎖定的Code
    }
    //dosomething
}
  • 優點:可以解決資源衝突,數據出現不可預測的異常錯誤。
  • 缺點:在同一個時間,只能被一個執行緒執行,它會拖慢多執行緒執行的速度。

還會有死鎖問題:

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
internal class StateObject
{
    private object _lock = new object();
    private int state = 5;
    public void ChangeState() 
    {
        //使用lock解決資源衝題的問題,但只會有一個執行緒會拿到
        lock (_lock) //會有死鎖問題,卡死了,鎖死了,程式無法執行
        {   //加上lock,這段代碼只有一個執行緒會執行
            if (state == 5)
            {
                state++; //讀寫
                Console.WriteLine($"State:{state}:{Thread.CurrentThread.ManagedThreadId}");
            }
            state = 5;
        }
    }
}
static void Main(string[] args)
{
    StateObject state = new StateObject();
    for (int i = 0; i < 100; i++)
    {
        Thread t = new Thread(state.ChangeState);
        t.Start();
    }
}

多執行緒死結的問題 lock

兩個執行緒(或者更多),因互相等待而造成死結。

死結寫法:

MethodA鎖住_lock1,MethodB鎖住_lock2 MethodA的方法裡,在等待_lock2釋放,MethodB的方法裡,在等待_lock1釋放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private object _lock1 = new object();
private object _lock2 = new object();

public void MethodA() {
    lock(_lock1) {
        lock(_lock2) {
            //todo
        }
    }
}
public void MethodB() {
    lock(_lock2) {
        lock(_lock1) {
            //todo
        }
    }
}

解決死結的方法:

大家都用一樣的鎖定順序就不會變死結了

1
2
3
4
5
6
7
8
9
10
11
12
public void MethodA() {
    lock(_lock1) {
        lock(_lock2) {
        }
    }
}
public void MethodB() {
    lock(_lock1) {
        lock(_lock2) {
        }
    }
}
This post is licensed under CC BY 4.0 by the author.