基礎拾遺
基礎拾遺——特性詳解
基礎拾遺——webservice詳解
基礎拾遺——redis詳解
基礎拾遺——反射詳解
基礎拾遺——委託、事件詳解
基礎拾遺——接口詳解
基礎拾遺——泛型詳解
基礎拾遺—–依賴注入
基礎拾遺—–數據註解與驗證
基礎拾遺—–mongoDB操作
基礎拾遺—-RabbitMQ
基礎拾遺—CLR
基礎拾遺—-多線程
前言
我們知道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(); } } }
注:但是有可能會出現線程爭用一直等待的情況,所以在編程過程設計好鎖的順序
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※超省錢租車方案
※教你寫出一流的銷售文案?
※網頁設計最專業,超強功能平台可客製化
※聚甘新