[筆記] C# 多執行緒與非同步
基本概念
多執行緒
執行緒可以分工加速,但多建立一條執行緒,就會消耗約 1 MB 的記憶體來配置
前臺執行緒
- 程式必須執行完前臺執行緒才會結束退出
- Thread 預設建立前臺執行緒
後臺執行緒
- 後臺執行緒在程式退出時就會結束
- 後臺執行緒結束時,程式並不會結束退出
- ThreadPool、Task、Parallel 預設建立後臺執行緒
非同步
非同步目的在於增加產能而非提高效能
- 當程式在等待時,可以先去執行另一項程式,不浪費等待時間
非同步不等於多執行緒
- 多執行緒的精神為分工加速,建立多個執行緒個別處理
- 非同步的重點在於允許執行緒在等待時間先處理其他工作
非同步對 I/O 相關工作較有效
- I/O 相關工作例如 : 呼叫 API、存取資料庫
- 要等待 I/O 回應的等待時間,可以先處理其他工作
- 而大量消耗 CPU 的重度運算,並無等待時間,使用多執行緒較有效率
四種建立執行緒的方法
歷史演變由上而下
- Thread
- ThreadPool
- Task
- Parallel
==TPL ( Task Parallel Library ) 是 .NET 4.0 中增加的平行運算函式庫,其中包含了 Task 類別與 Parallel 類別==
Thread
使用 System.Threading.Thread 類別
執行不帶參數的方法
開啟執行緒
1
2Thread thread = new Thread(new ThreadStart(Method));
thread.Start(); //並不是馬上執行,而是準備好被 CPU 執行,甚麼時候執行視情況而定方法
1
2
3
4
5public void Method()
{
Thread.Sleep(1000); //單位為毫秒
Console.WriteLine("thread done");
}
執行帶參數的方法
開啟執行緒
1
2Thread thread = new Thread(new ParameterizedThreadStart(Method));
thread.Start("thread done");方法
1
2
3
4
5public void Method(string str)
{
Thread.Sleep(1000);
Console.WriteLine(str);
}
ThreadPool
使用 System.Threading.ThreadPool 類別
為 Thread 的升級版
執行不帶參數的方法
1
ThreadPool.QueueUserWorkItem(new WaitCallback(x => Console.WriteLine("thread done")));
執行帶參數的方法
1
ThreadPool.QueueUserWorkItem(new WaitCallback(x => Console.WriteLine(x)), "thread done");
與 Thread 的差別 :
建立執行緒
- Thread : 每次都建立一個新的執行緒
- ThreadPool : 會查看執行緒池,若無空閒的執行緒才建立,剛開始執行緒池是沒有執行緒的,當 ThreadPool 建立一個執行緒後,此執行緒才會加入執行緒池
操控執行緒的狀態
- ThreadPool 可以操控執行緒的狀態,例如 : 等待執行緒完成、中止超時的執行緒等,Thread 則不行
Task
使用 System.Threading.Tasks.Task 類別
與 ThreadPool 是一樣使用執行緒池的
兩種建立方式
使用 Task 的 Run 方法
1
2
3Task.Run(()=> {
Console.WriteLine("thread done");
});使用 TaskFactory 物件的 StartNew 方法
1
2
3
4(new TaskFactory()).StartNew(() =>
{
Console.WriteLine("thread done");
});
方法有返回值
1
2
3
4
5Task<string> task = Task.Run<string>(() => {
return "thread done";
});
Console.WriteLine(task.Result);取消超時執行緒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//1秒後自動取消執行緒
CancellationTokenSource cts = new CancellationTokenSource(1000);
cts.Token.Register(()=> {
Console.WriteLine("thread cancle");
});
Task.Run(()=> {
Console.WriteLine("thread start");
Thread.Sleep(2000);
if (cts.Token.IsCancellationRequested) {
Console.WriteLine("thread stop");
return;
}
Console.WriteLine("thread done");
}, cts.Token);等待所有執行緒執行完畢
1
Task.WaitAll(Task1, Task2, Task3);
等待任意一個執行緒執行完畢
1
Task.WaitAny(Task1, Task2, Task3);
與 ThreadPool 差別在於 Task 在多核 CPU 時效能較優,因為 ThreadPool 使用的執行緒池是全域,會造成資源共享的競爭,且 Task 提供較豐富的 API 方法
Parallel
使用 System.Threading.Tasks.Parallel 類別
Parallel.Invoke()
1
2
3
4
5
6Action[] action = new Action[] {
()=>Console.WriteLine($"thread:{Thread.CurrentThread.ManagedThreadId}"),
()=>Console.WriteLine($"thread:{Thread.CurrentThread.ManagedThreadId}"),
()=>Console.WriteLine($"thread:{Thread.CurrentThread.ManagedThreadId}"),
};
Parallel.Invoke(action);Parallel.For()
1 | ParallelLoopResult plr = Parallel.For(1, 10, (i) => |
- Parallel.ForEach()
1 | Parallel.ForEach<String>(new List<String>() { |
- 與 Task 差別在於執行緒的數量控制較為簡單
使用非同步的方法
async/await
為非同步的修飾詞
- async 用來修飾方法,await 用來呼叫方法
- await 必須出現在有 async 修飾的方法中
- await 呼叫的方法可以不用 async 修飾,但返回值必須為
Task<T>
型別
async 像病毒一樣會傳染
- 當方法前加上 async 後,裡面若呼叫外部方法必須加上 await
- 此設計是為了避免同步與非同步寫法混用
將同步程式重構為非同步
- 建議採由下而上 ( Bottom-Up ) 的策略
- 若由上開始,所有呼叫到的方法都必須更改