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) {
}
}
}
|