基本概念
多執行緒
執行緒可以分工加速,但多建立一條執行緒,就會消耗約 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
2
| Thread thread = new Thread(new ThreadStart(Method)); //建立一條執行緒去執行 Method方法
thread.Start(); //並不是馬上執行,而是準備好被 CPU 執行,甚麼時候執行視情況而定
|
Method 方法
1
2
3
4
5
| public void Method()
{
Thread.Sleep(1000); //睡一秒。單位為毫秒
Console.WriteLine("thread done");
}
|
執行帶參數的方法
開啟執行緒
1
2
| Thread thread = new Thread(new ParameterizedThreadStart(Method)); //建立一條執行緒去執行 Method方法
thread.Start("thread done"); //將字串帶入Method方法
|
Method 方法
1
2
3
4
5
| public 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
3
| Task.Run(()=> {
Console.WriteLine("thread done");
});
|
使用 TaskFactory 物件的 StartNew 方法
1
2
3
4
| (new TaskFactory()).StartNew(() =>
{
Console.WriteLine("thread done");
});
|
方法有返回值
1
2
3
4
5
| Task<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
6
| Action[] 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
2
3
4
5
| ParallelLoopResult plr = Parallel.For(1, 10, (i) =>
{
Console.WriteLine($"thread:{Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine(plr.IsCompleted);
|
Parallel.ForEach()
1
2
3
4
5
6
| Parallel.ForEach<String>(new List<String>() {
"a","b","c","d","e","f","g","h","i"
}, (str) =>
{
Console.WriteLine(str);
});
|
與 Task 差別在於執行緒的數量控制較為簡單
使用非同步的方法
async/await
為非同步的修飾詞
async 用來修飾方法,await 用來呼叫方法await 必須出現在有 async 修飾的方法中await 呼叫的方法可以不用 async 修飾,但返回值必須為 Task<T> 型別
async 像病毒一樣會傳染
- 當方法前加上
async 後,裡面若呼叫外部方法必須加上 await - 此設計是為了避免同步與非同步寫法混用
將同步程式重構為非同步
- 建議採由下而上 ( Bottom-Up ) 的策略
- 若由上開始,所有呼叫到的方法都必須更改
筆記 - C# 多執行緒與非同步
C#:前台线程后台线程