基礎拾遺—-多執行緒
基礎拾遺
前言
我們知道c# 程式是自上而下的,但有的時候部分程式使用時間較長比如下載文檔什麼的。這是就可以用到執行緒。執行緒可以理解為是程式的執行路徑,每個執行緒都定義了一個獨特的控制流。如果您的應用程式涉及到複雜的和耗時的操作,那麼設置不同的執行緒執行路徑往往是有益的,每個執行緒執行特定的工作。
1.執行緒的生命周期
執行緒生命周期開始於 System.Threading.Thread 類的對象被創建時,結束於執行緒被終止或完成執行時。
下面列出了執行緒生命周期中的各種狀態:
未啟動狀態:當執行緒實例被創建但 Start 方法未被調用時的狀況。
就緒狀態:當執行緒準備好運行並等待 CPU 周期時的狀況。
不可運行狀態:下面的幾種情況下執行緒是不可運行的:
已經調用 Sleep 方法
已經調用 Wait 方法
通過 I/O 操作阻塞
死亡狀態:當執行緒已完成執行或已中止時的狀況。
2.多執行緒的優缺點
2.1.優點
- 可以使用執行緒將程式碼同其他程式碼隔離,提高應用程式的可靠性。
- 可以使用執行緒來簡化編碼。
- 可以使用執行緒來實現並發執行。
- 可以提高CPU的利用率
2.2.缺點
- 執行緒開的越多,記憶體佔用越大。
- 協調和管理程式碼的難度加大,需要CPU時間跟蹤執行緒。
- 執行緒之間對共享資源的訪問會相互影響,必須解決競用共享資源的問題。
- 銷毀執行緒需要了解可能發生的問題並對那些問題進行處理。
3.執行緒的實現
3.1.非同步委託
關於委託基礎拾遺——委託、事件詳解這有詳細介紹,我們都知道調用委託Delegate()或者Delegate?.Invoke()。進行執行,但是主執行緒的程式碼是從上至下進行執行的,那麼我們想要委託方法進行一個新的執行緒只需BeginInvoke生成非同步方法調用即可。
3.3.1.實現
public delegate void ThreadDelegate(); static void MethodDelegata() { Console.WriteLine("MethodDelegata"); } static void Main(string[] args) { ThreadDelegate d = MethodDelegata; //BeginInvoke 兩個參數一個是執行完後回調方法,一個是返回結果,如委託有參數載氣前方添加即可。 d.BeginInvoke(null,null); Console.WriteLine("Main"); Console.ReadKey(); }
執行結果如下

3.1.1.獲取執行緒返回值
執行緒執行時有可能執行時間過長,如果我們要獲取執行緒的返回值,這是就需要不回執行緒的狀態和利用執行緒的回調方法。
- 檢測等待執行緒狀態
public delegate int ThreadDelegate(int i); static int MethodDelegata(int i) { Console.WriteLine("MethodDelegata" + i); Thread.Sleep(1000); return 100; } static void Main(string[] args) { ThreadDelegate d = MethodDelegata; //BeginInvoke 兩個參數一個是執行完後回調方法,一個是返回結果 IAsyncResult ar = d?.BeginInvoke(1, null, null);//獲取執行緒執行狀態 Console.WriteLine("Main"); while (!ar.IsCompleted) {//執行緒是否已執行完成,未完成執行 Console.WriteLine("."); Thread.Sleep(10);//減少執行緒監測頻率 } int res = d.EndInvoke(ar);//獲取執行緒的返回值 Console.WriteLine(res); Console.ReadKey(); }
結果如下
·
我們如果不用while 的方式去等待方法執行結束,可以 ar.AsyncWaitHandle.WaitOne(1000); 但我們預估執行時間如果小於實際執行時間的化,返回值就獲取不到了。

bool isEnd = ar.AsyncWaitHandle.WaitOne(1000); if (isEnd) { int res = d.EndInvoke(ar);//獲取執行緒的返回值 Console.WriteLine(res); }
View Code
- 利用 d?.BeginInvoke(1, callBack, object) 回調方法
public delegate int ThreadDelegate(int i); static int MethodDelegata(int i) { Console.WriteLine("MethodDelegata" + i); Thread.Sleep(1000); return 100; } static void Main(string[] args) { ThreadDelegate d = MethodDelegata; //BeginInvoke 兩個參數一個是執行完後回調方法,一個是返回結果 IAsyncResult ar = d?.BeginInvoke(1, CallBack, d);//獲取執行緒執行狀態 Console.WriteLine("Main"); Console.ReadKey(); } /// <summary> /// 結束回調方法 /// </summary> /// <param name="ar"></param> private static void CallBack(IAsyncResult ar) { var obj=ar.AsyncState as ThreadDelegate; int res = obj.EndInvoke(ar); Console.WriteLine("執行緒結束,結果為:"+res); }

我們通過lamda表達式優化一下上面的程式碼
static void Main(string[] args) { ThreadDelegate d = MethodDelegata; //BeginInvoke 兩個參數一個是執行完後回調方法,一個是返回結果 //IAsyncResult ar = d?.BeginInvoke(1, CallBack, d);//獲取執行緒執行狀態 d?.BeginInvoke(1, ar => { int res = d.EndInvoke(ar); Console.WriteLine("執行緒結束,結果為:" + res); }, null); Console.WriteLine("Main"); Console.ReadKey(); }
3.2.Thread 類
3.2.1.不帶參數
static void MethodThread() { Console.WriteLine("MethodDelegata");//第二個參數最多執行時間 Thread.Sleep(1000); } static void Main(string[] args) { Thread t = new Thread(MethodThread);//創建了thread 對象單位啟動 //Thread t = new Thread(()=> { Console.WriteLine("MethodDelegata"); Thread.Sleep(1000);});//可直接用lamda表達式 t.Start(); Console.WriteLine("Main"); Console.ReadKey(); }
3.2.2.帶參數
Start(obj) 傳參:定義方法如果有參數必須object
static void MethodThread(object s) { Console.WriteLine("MethodDelegata"); Thread.Sleep(1000); } static void Main(string[] args) { //創建了thread 對象單位啟動 Thread t = new Thread(MethodThread); t.Start("wokao");//傳遞參數 Console.WriteLine("Main"); Console.ReadKey(); }
對象傳參:定義存放數據和執行緒方法的類
class Program { static void Main(string[] args) { //創建了thread 對象單位啟動 ClassThead c = new ClassThead("1"); Thread t = new Thread(c.MethodThread); t.Start();//傳遞參數 Console.WriteLine("Main"); Console.ReadKey(); } } public class ClassThead { private string wr; public ClassThead(string s) { this.wr = s; } public void MethodThread() { Console.WriteLine("MethodDelegata"); Thread.Sleep(1000); } }
3.2.3 前台執行緒和後台執行緒
- 前台執行緒:只要存在有一個前台執行緒在運行,應用程式就在運行。
- 後台執行緒:應用程式關閉時,如果後台執行緒沒有執行完,會被強制性的關閉
- 默認情況下,用Thread類創建的執行緒是前台執行緒,執行緒池中的執行緒總是後台執行緒。
- thread.IsBackground = true; 設置為後台程式
static void MethodThread() { Console.WriteLine("MethodDelegata"); Thread.Sleep(10000); Console.ReadKey(); } static void Main(string[] args) { Thread t = new Thread(MethodThread); t.IsBackground = true;//當main執行結束後,不管t是否執行結束程式都關閉 t.Start();//傳遞參數 Console.WriteLine("Main"); }
thread.Abort() 終止執行緒的執行。調用這個方法,會拋出一個ThreadAbortException類型的異常。
thread.Join() 將當前執行緒睡眠,等待thread執行緒執行完,然後再繼續執行當前執行緒。
3.3.執行緒池threadPool
上面已經說了執行緒是為後台執行緒,在這多執行緒的操作推薦使用執行緒池執行緒而非新建執行緒。因為就算只是單純的新建一個執行緒,這個執行緒什麼事情也不做,都大約需要1M的記憶體空間來存儲執行上下文數據結構,並且執行緒的創建與回收也需要消耗資源,耗費時間。而執行緒池的優勢在於執行緒池中的執行緒是根據需要創建與銷毀,是最優的存在。但是這也有個問題,那就是執行緒池執行緒都是後台執行緒,主執行緒執行完畢後,不會等待後台執行緒而直接結束程式。
//如果帶參數必須為object static void MethodThreadPool(object obj) { Console.WriteLine("MethodDelegata"+Thread.CurrentThread.ManagedThreadId);//當前執行緒id Thread.Sleep(1000); } static void Main(string[] args) { ThreadPool.QueueUserWorkItem(MethodThreadPool);// 必須帶參數 ThreadPool.QueueUserWorkItem(MethodThreadPool); ThreadPool.QueueUserWorkItem(MethodThreadPool); ThreadPool.QueueUserWorkItem(MethodThreadPool); ThreadPool.QueueUserWorkItem(MethodThreadPool); Console.WriteLine("Main"); Console.ReadKey(); }

3.4. Task
- Task是架構在Thread之上的,也就是說任務最終還是要拋給執行緒去執行。
- Task跟Thread不是一對一的關係,比如開10個任務並不是說會開10個執行緒,這一點任務有點類似執行緒池,但是任務相比執行緒池有很小的開銷和精確的控制
- 可以將任務入隊到執行緒池中非同步執行。
- 執行緒池入隊的任務無法取消
- 沒有回調方法,可以使用委託實現回調
3.4.1.任務的定義
方法1
var t1 = new Task(() => TaskMethod("Task 1")); t1.Start(); Task.WaitAll(t1);//等待所有任務結束
方法2
Task.Run(() => TaskMethod("Task 2"));
方法3
Task.Factory.StartNew(() => TaskMethod("Task 3")); 直接非同步的方法 //或者 var t3=Task.Factory.StartNew(() => TaskMethod("Task 3")); Task.WaitAll(t3);//等待所有任務結束
案列

static void Main(string[] args) { var t1 = new Task(() => TaskMethod("Task 1")); var t2 = new Task(() => TaskMethod("Task 2")); t2.Start(); t1.Start(); Task.WaitAll(t1, t2); Task.Run(() => TaskMethod("Task 3")); Task.Factory.StartNew(() => TaskMethod("Task 4")); //標記為長時間運行任務,則任務不會使用執行緒池,而在單獨的執行緒中運行。 Task.Factory.StartNew(() => TaskMethod("Task 5"), TaskCreationOptions.LongRunning); Console.WriteLine("主執行緒執行業務處理."); //創建任務 Task task = new Task(() => { Console.WriteLine("使用System.Threading.Tasks.Task執行非同步操作."); for (int i = 0; i < 10; i++) { Console.WriteLine(i); } }); //啟動任務,並安排到當前任務隊列執行緒中執行任務(System.Threading.Tasks.TaskScheduler) task.Start(); Console.WriteLine("主執行緒執行其他處理"); task.Wait(); Thread.Sleep(TimeSpan.FromSeconds(1)); Console.ReadLine(); } static void TaskMethod(string name) { Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}", name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); }
View Code
3.4.2.async/await
async是contextual關鍵字,await是運算符關鍵字。
async/await 結構可分成三部分:
- 調用方法:該方法調用非同步方法,然後在非同步方法執行其任務的時候繼續執行;
- 非同步方法:該方法非同步執行工作,然後立刻返回到調用方法;
- await 表達式:用於非同步方法內部,指出需要非同步執行的任務。一個非同步方法可以包含多個 await 表達式(不存在 await 表達式的話 IDE 會發出警告)。
class Program { async static void AsyncFunction() { await Task.Delay(1); Console.WriteLine("使用System.Threading.Tasks.Task執行非同步操作."); for (int i = 0; i < 10; i++) { Console.WriteLine(string.Format("AsyncFunction:i={0}", i)); } } public static void Main() { Console.WriteLine("主執行緒執行業務處理."); AsyncFunction(); Console.WriteLine("主執行緒執行其他處理"); for (int i = 0; i < 10; i++) { Console.WriteLine(string.Format("Main:i={0}", i)); } Console.ReadLine(); } }
4.執行緒爭用與死鎖

class Program { static void ChangeState(object obj) { ClassThead c = obj as ClassThead; while (true) { c.MethodThread(); } } //如果帶參數必須為object static void Main(string[] args) { ClassThead c = new ClassThead(); Thread t = new Thread(ChangeState); t.Start(c); Console.WriteLine("Main"); Console.ReadKey(); } } public class ClassThead { private int state = 6; public void MethodThread() { state++; if (state == 6) { Console.WriteLine("MethodDelegata"); Thread.Sleep(1000); } state = 6; } }
View Code
可以從上面的方法中看到執行結果為空,雖然他在執行但是state一直都是>6的。所以是不執行的。
但如果開啟兩個執行緒的結果是什麼呢?

是執行的因為多個執行緒有可能是在執行時另一個執行緒給他賦值了。所以我們就要給對象加鎖
static void ChangeState(object obj) { ClassThead c = obj as ClassThead; while (true) { lock (c) { c.MethodThread(); } } }
註:但是有可能會出現執行緒爭用一直等待的情況,所以在編程過程設計好鎖的順序


