c#之多線程之為所欲為
一 什麽是多線程
1、 什麽是進程?一個 exe 運行一次就會產生一個進程,一個 exe 的多個進程之 間數據互相隔離。
2、 一個進程裏至少有一個線程:主線程。我們平時寫的控制臺程序默認就是單線程的,代 碼從上往下執行,一行執行完了再執行下一行;
3、 什麽是多線程:一個人兩件事情同時做效率高。同一時刻一 個人只能幹一件事情,其實是在“快速頻繁切換”,如果處理不當可能比不用多線程效率還低
二 Thread 對象
2.1 thread基礎寫法
public static void ThreadTest() { int a = 0; Thread thread1 = new Thread(m=> { for (int i = 0; i < 20; i++) { a = a + 1; Console.WriteLine("線程1:"+ a); } }); Thread thread2 = new Thread(m => { for (int i = 0; i < 20; i++) { a = a + 1; Console.WriteLine("線程2:"+ a); } }); thread1.Start(); thread2.Start(); Console.ReadKey(); }
這段代碼輸出結果如下:
可以看出兩個子線程啟動後是並行執行的,所以輸出結果沒有按照順序來
2.2 設置線程的優先級
thread1.Priority=ThreadPriority。。。
2.3 t1.Join()當前線程等待 t1 線程執行結束,實例如下:
這段代碼執行過後輸出的結果就是正常的從1輸出到了40
public static void ThreadTest() { int a = 0; Thread thread1 = new Thread(m=> {for (int i = 0; i < 20; i++) { a = a + 1; Console.WriteLine("線程1:"+ a); } }); Thread thread2 = new Thread(m => { //等待thread1線程任務完成後在執行 thread1.Join();for (int i = 0; i < 20; i++) { a = a + 1; Console.WriteLine("線程2:"+ a); } }); //可以將參數傳入到子線程中 thread1.Start(a); //thread1.Join(); 或者將Join放在這裏 thread2.Start(a); Console.ReadKey(); }
2.4 Interrupt方法
Interrupt 用於提前喚醒一個在 Sleep 的線程,Sleep 方法會拋出 ThreadInterruptedException 異常 代碼如下:
代碼輸出到9的時候線程會休眠8秒鐘,但是運行到主線程thread1.Interrupt()時,子線程會被喚醒,然後執行catch裏面的Console.WriteLine("線程被喚醒");之後接著從10開始輸出到2000。需要註意的是只有線程自身能讓自身休眠
public static void ThreadTest2() { Thread thread1 = new Thread(() => { for (int i = 0; i < 2000; i++) { if (i==10) { //喚醒線程之後會引發ThreadInterruptedException類型的異常,所以需要try catch try { //子線程休眠8秒鐘 Thread.Sleep(8000); } catch (ThreadInterruptedException ex) { Console.WriteLine("線程被喚醒"); } } Console.WriteLine(i); } }); thread1.Start(); //提前喚醒在沈睡的子線程 Thread.Sleep(3000); thread1.Interrupt(); Console.ReadKey(); }
三 線程池
3.1、線程池:因為每次創建線程、銷毀線程都比較消耗 cpu 資源,因此可以通過線程池進行 優化。線程池是一組已經創建好的線程,隨用隨取,用完了不是銷毀線程,然後放到線程池 中,供其他人用。
3.2、用線程池之後就無法對線程進行精細化的控制了(線程啟停、優先級控制等)
3.3、ThreadPool 類的一個重要方法:
static bool QueueUserWorkItem(WaitCallback callBack)
static bool QueueUserWorkItem(WaitCallback callBack, object state)
3.4、除非要對線程進行精細化的控制,否則建議使用線程池,因為又簡單、性能調優又更好。
//QueueUserWorkItem是一個靜態方法不需要New public static void ThreadPool() { System.Threading.ThreadPool.QueueUserWorkItem(m=> { for (int i = 0; i < 1000; i++) { i++; Console.WriteLine(i); } }); Console.ReadKey(); }
四 TPL風格的異步方法
TPL(Task Parallel Library)是.Net 4.0 之後帶來的新特性,更簡潔,更方便。現在在.Net 平臺下已經大面積使用。
註意方法中如果有 await,則方法必須標記為 async,不是所有方法都可以被輕松的標記 為 async。WinForm 中的事件處理方法都可以標記為 async、MVC 中的 Action 方法也可以標 記為 async、控制臺的 Main 方法不能標記為 async。 TPL 的特點是:方法都以 XXXAsync 結尾,返回值類型是泛型的 Task<T>。 TPL 讓我們可以用線性的方式去編寫異步程序,不再需要像 EAP 中那樣搞一堆回調、邏 輯跳來跳去了。
/TPL風格返回的Task<T> 泛型的數據 //await 關鍵字等待異步方法返回 public static async void Task() { WebClient wc = new WebClient(); string s= await wc.DownloadStringTaskAsync("http://www.baidu.com"); Console.WriteLine(s); Console.ReadKey(); } public static void Task2() { WebClient wc = new WebClient(); //若果不使用await關鍵字就得使用Task<string>類型來接收數據 Task<string> s2 = wc.DownloadStringTaskAsync("http://www.baidu.com"); Console.ReadKey(); }
自己編寫一個TPL風格的異步方法:
使用了async關鍵字就必須返回Task泛型數據類型的數據
public static Task<string> StringAsync() { return Task.Run(() => { Thread.Sleep(5000); return "hehe"; }); } // GET: Home public async Task<ViewResult> Index() { var s = await StringAsync(); return View(); }
如果返回值就是一個立即可以隨手可得的值,那麽就用 Task.FromResult() 如果是一個需要休息一會的任務(比如下載失敗則過 5 秒鐘後重試。主線程不休息,和 Thread.Sleep 不一樣),那麽就用 Task.Delay()。 3、Task.Factory.FromAsync()把 IAsyncResult 轉換為 Task,這樣 APM 風格的 api 也可以用 await 來調 用。 4、編寫異步方法的簡化寫法。如果方法聲明為 async,那麽可以直接 return 具體的值,不再用創建 Task,由編譯器創建 Task:
static async Task<int> F1Async() { return 1; } static Task<int> F2Async() { return Task.FromResult(3); } static Task<int> F3Async() { return Task.Run(()=> { return 1 + 3; }); }
一定要讓 async 的傳染性(調用異步方法要用 await,用了 await 方法就要聲明為 async,調 用我這個 async 方法的地方必須要 await……)不要輕易直接調用 Task 的 Wait、WaitAll 等方 法。等待一個用 await,而不是 task.Wait();等待多個用 await Task.WhenAll(),而不是 Task.WaitAll()
c#之多線程之為所欲為