詳解C#中 Thread,Task,Async/Await,IAsyncResult的那些事兒
說起異步,Thread,Task,async/await,IAsyncResult 這些東西肯定是繞不開的,今天就來依次聊聊他們
1.線程(Thread)
多線程的意義在于一個應用程序中,有多個執(zhí)行部分可以同時執(zhí)行;對于比較耗時的操作(例如io,數(shù)據(jù)庫操作),或者等待響應(如WCF通信)的操作,可以單獨開啟后臺線程來執(zhí)行,這樣主線程就不會阻塞,可以繼續(xù)往下執(zhí)行;等到后臺線程執(zhí)行完畢,再通知主線程,然后做出對應操作!
在C#中開啟新線程比較簡單
static void Main(string[] args) { Console.WriteLine("主線程開始"); //IsBackground=true,將其設置為后臺線程 Thread t = new Thread(Run) { IsBackground = true }; t.Start(); Console.WriteLine("主線程在做其他的事!"); //主線程結束,后臺線程會自動結束,不管有沒有執(zhí)行完成 //Thread.Sleep(300); Thread.Sleep(1500); Console.WriteLine("主線程結束"); } static void Run() { Thread.Sleep(700); Console.WriteLine("這是后臺線程調用"); }
執(zhí)行結果如下圖,
可以看到在啟動后臺線程之后,主線程繼續(xù)往下執(zhí)行了,并沒有等到后臺線程執(zhí)行完之后。
1.1 線程池
試想一下,如果有大量的任務需要處理,例如網(wǎng)站后臺對于HTTP請求的處理,那是不是要對每一個請求創(chuàng)建一個后臺線程呢?顯然不合適,這會占用大量內存,而且頻繁地創(chuàng)建的過程也會嚴重影響速度,那怎么辦呢?線程池就是為了解決這一問題,把創(chuàng)建的線程存起來,形成一個線程池(里面有多個線程),當要處理任務時,若線程池中有空閑線程(前一個任務執(zhí)行完成后,線程不會被回收,會被設置為空閑狀態(tài)),則直接調用線程池中的線程執(zhí)行(例asp.net處理機制中的Application對象),
使用事例:
for (int i = 0; i < 10; i++) { ThreadPool.QueueUserWorkItem(m => { Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString()); }); } Console.Read();
運行結果:
可以看到,雖然執(zhí)行了10次,但并沒有創(chuàng)建10個線程。
1.2 信號量(Semaphore)
Semaphore負責協(xié)調線程,可以限制對某一資源訪問的線程數(shù)量
這里對SemaphoreSlim類的用法做一個簡單的事例:
static SemaphoreSlim semLim = new SemaphoreSlim(3); //3表示最多只能有三個線程同時訪問 static void Main(string[] args) { for (int i = 0; i < 10; i++) { new Thread(SemaphoreTest).Start(); } Console.Read(); } static void SemaphoreTest() { semLim.Wait(); Console.WriteLine("線程" + Thread.CurrentThread.ManagedThreadId.ToString() + "開始執(zhí)行"); Thread.Sleep(2000); Console.WriteLine("線程" + Thread.CurrentThread.ManagedThreadId.ToString() + "執(zhí)行完畢"); semLim.Release(); }
執(zhí)行結果如下:
可以看到,剛開始只有三個線程在執(zhí)行,當一個線程執(zhí)行完畢并釋放之后,才會有新的線程來執(zhí)行方法!
除了SemaphoreSlim類,還可以使用Semaphore類,感覺更加靈活,感興趣的話可以搜一下,這里就不做演示了!
2.Task
Task是.NET4.0加入的,跟線程池ThreadPool的功能類似,用Task開啟新任務時,會從線程池中調用線程,而Thread每次實例化都會創(chuàng)建一個新的線程。
Console.WriteLine("主線程啟動"); //Task.Run啟動一個線程 //Task啟動的是后臺線程,要在主線程中等待后臺線程執(zhí)行完畢,可以調用Wait方法 //Task task = Task.Factory.StartNew(() => { Thread.Sleep(1500); Console.WriteLine("task啟動"); }); Task task = Task.Run(() => { Thread.Sleep(1500); Console.WriteLine("task啟動"); }); Thread.Sleep(300); task.Wait(); Console.WriteLine("主線程結束");
執(zhí)行結果如下:
開啟新任務的方法:Task.Run()或者Task.Factory.StartNew(),開啟的是后臺線程
要在主線程中等待后臺線程執(zhí)行完畢,可以使用Wait方法(會以同步的方式來執(zhí)行)。不用Wait則會以異步的方式來執(zhí)行。
比較一下Task和Thread:
static void Main(string[] args) { for (int i = 0; i < 5; i++) { new Thread(Run1).Start(); } for (int i = 0; i < 5; i++) { Task.Run(() => { Run2(); }); } } static void Run1() { Console.WriteLine("Thread Id =" + Thread.CurrentThread.ManagedThreadId); } static void Run2() { Console.WriteLine("Task調用的Thread Id =" + Thread.CurrentThread.ManagedThreadId); }
執(zhí)行結果:
可以看出來,直接用Thread會開啟5個線程,用Task(用了線程池)開啟了3個!
2.1 Task<TResult>
Task<TResult>就是有返回值的Task,TResult就是返回值類型。
Console.WriteLine("主線程開始"); //返回值類型為string Task<string> task = Task<string>.Run(() => { Thread.Sleep(2000); return Thread.CurrentThread.ManagedThreadId.ToString(); }); //會等到task執(zhí)行完畢才會輸出; Console.WriteLine(task.Result); Console.WriteLine("主線程結束");
運行結果:
通過task.Result可以取到返回值,若取值的時候,后臺線程還沒執(zhí)行完,則會等待其執(zhí)行完畢!
簡單提一下:
Task任務可以通過CancellationTokenSource類來取消,感覺用得不多,用法比較簡單,感興趣的話可以搜一下!
3. async/await
async/await是C#5.0中推出的,先上用法:
static void Main(string[] args) { Console.WriteLine("-------主線程啟動-------"); Task<int> task = GetStrLengthAsync(); Console.WriteLine("主線程繼續(xù)執(zhí)行"); Console.WriteLine("Task返回的值" + task.Result); Console.WriteLine("-------主線程結束-------"); } static async Task<int> GetStrLengthAsync() { Console.WriteLine("GetStrLengthAsync方法開始執(zhí)行"); //此處返回的<string>中的字符串類型,而不是Task<string> string str = await GetString(); Console.WriteLine("GetStrLengthAsync方法執(zhí)行結束"); return str.Length; } static Task<string> GetString() { //Console.WriteLine("GetString方法開始執(zhí)行") return Task<string>.Run(() => { Thread.Sleep(2000); return "GetString的返回值"; }); }
async用來修飾方法,表明這個方法是異步的,聲明的方法的返回類型必須為:void,Task或Task<TResult>。
await必須用來修飾Task或Task<TResult>,而且只能出現(xiàn)在已經(jīng)用async關鍵字修飾的異步方法中。通常情況下,async/await成對出現(xiàn)才有意義,
看看運行結果:
可以看出來,main函數(shù)調用GetStrLengthAsync方法后,在await之前,都是同步執(zhí)行的,直到遇到await關鍵字,main函數(shù)才返回繼續(xù)執(zhí)行。
那么是否是在遇到await關鍵字的時候程序自動開啟了一個后臺線程去執(zhí)行GetString方法呢?
現(xiàn)在把GetString方法中的那行注釋加上,運行的結果是:
大家可以看到,在遇到await關鍵字后,沒有繼續(xù)執(zhí)行GetStrLengthAsync方法后面的操作,也沒有馬上反回到main函數(shù)中,而是執(zhí)行了GetString的第一行,以此可以判斷await這里并沒有開啟新的線程去執(zhí)行GetString方法,而是以同步的方式讓GetString方法執(zhí)行,等到執(zhí)行到GetString方法中的Task<string>.Run()的時候才由Task開啟了后臺線程!
那么await的作用是什么呢?
可以從字面上理解,上面提到task.wait可以讓主線程等待后臺線程執(zhí)行完畢,await和wait類似,同樣是等待,等待Task<string>.Run()開始的后臺線程執(zhí)行完畢,不同的是await不會阻塞主線程,只會讓GetStrLengthAsync方法暫停執(zhí)行。
那么await是怎么做到的呢?有沒有開啟新線程去等待?
只有兩個線程(主線程和Task開啟的線程)!至于怎么做到的(我也不知道......>_<),大家有興趣的話研究下吧!
4.IAsyncResult
IAsyncResult自.NET1.1起就有了,包含可異步操作的方法的類需要實現(xiàn)它,Task類就實現(xiàn)了該接口
在不借助于Task的情況下怎么實現(xiàn)異步呢?
class Program { static void Main(string[] args) { Console.WriteLine("主程序開始--------------------"); int threadId; AsyncDemo ad = new AsyncDemo(); AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod); IAsyncResult result = caller.BeginInvoke(3000,out threadId, null, null); Thread.Sleep(0); Console.WriteLine("主線程線程 {0} 正在運行.",Thread.CurrentThread.ManagedThreadId) //會阻塞線程,直到后臺線程執(zhí)行完畢之后,才會往下執(zhí)行 result.AsyncWaitHandle.WaitOne(); Console.WriteLine("主程序在做一些事情?。?!"); //獲取異步執(zhí)行的結果 string returnValue = caller.EndInvoke(out threadId, result); //釋放資源 result.AsyncWaitHandle.Close(); Console.WriteLine("主程序結束--------------------"); Console.Read(); } } public class AsyncDemo { //供后臺線程執(zhí)行的方法 public string TestMethod(int callDuration, out int threadId) { Console.WriteLine("測試方法開始執(zhí)行."); Thread.Sleep(callDuration); threadId = Thread.CurrentThread.ManagedThreadId; return String.Format("測試方法執(zhí)行的時間 {0}.", callDuration.ToString()); } } public delegate string AsyncMethodCaller(int callDuration, out int threadId);
關鍵步驟就是紅色字體的部分,運行結果:
和Task的用法差異不是很大!result.AsyncWaitHandle.WaitOne()就類似Task的Wait。
5.Parallel
最后說一下在循環(huán)中開啟多線程的簡單方法:
Stopwatch watch1 = new Stopwatch(); watch1.Start(); for (int i = 1; i <= 10; i++) { Console.Write(i + ","); Thread.Sleep(1000); } watch1.Stop(); Console.WriteLine(watch1.Elapsed); Stopwatch watch2 = new Stopwatch(); watch2.Start(); //會調用線程池中的線程 Parallel.For(1, 11, i => { Console.WriteLine(i + ",線程ID:" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(1000); }); watch2.Stop(); Console.WriteLine(watch2.Elapsed);
運行結果:
循環(huán)List<T>:
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6, 6, 7, 8, 9 }; Parallel.ForEach<int>(list, n => { Console.WriteLine(n); Thread.Sleep(1000); });
執(zhí)行Action[]數(shù)組里面的方法:
Action[] actions = new Action[] { new Action(()=>{ Console.WriteLine("方法1"); }), new Action(()=>{ Console.WriteLine("方法2"); }) }; Parallel.Invoke(actions);
以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持我們!
上一篇:C#利用性能計數(shù)器監(jiān)控網(wǎng)絡狀態(tài)
欄 目:C#教程
下一篇:C#中常用的正則表達式
本文標題:詳解C#中 Thread,Task,Async/Await,IAsyncResult的那些事兒
本文地址:http://mengdiqiu.com.cn/a1/C_jiaocheng/6029.html
您可能感興趣的文章
- 01-10C#通過反射獲取當前工程中所有窗體并打開的方法
- 01-10C#實現(xiàn)Winform中打開網(wǎng)頁頁面的方法
- 01-10C#實現(xiàn)由四周向中心縮小的窗體退出特效
- 01-10Extjs4如何處理后臺json數(shù)據(jù)中日期和時間
- 01-10C#中DataGridView常用操作實例小結
- 01-10C#編程獲取資源文件中圖片的方法
- 01-10asp.net中XML如何做增刪改查操作
- 01-10C#利用反射技術實現(xiàn)去掉按鈕選中時的邊框效果
- 01-10C#中查找Dictionary中的重復值的方法
- 01-10C#中TreeView實現(xiàn)適合兩級節(jié)點的選中節(jié)點方法


閱讀排行
本欄相關
- 01-10C#通過反射獲取當前工程中所有窗體并
- 01-10關于ASP網(wǎng)頁無法打開的解決方案
- 01-10WinForm限制窗體不能移到屏幕外的方法
- 01-10WinForm繪制圓角的方法
- 01-10C#實現(xiàn)txt定位指定行完整實例
- 01-10WinForm實現(xiàn)仿視頻 器左下角滾動新
- 01-10C#停止線程的方法
- 01-10C#實現(xiàn)清空回收站的方法
- 01-10C#通過重寫Panel改變邊框顏色與寬度的
- 01-10C#實現(xiàn)讀取注冊表監(jiān)控當前操作系統(tǒng)已
隨機閱讀
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 01-10C#中split用法實例總結
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 01-10SublimeText編譯C開發(fā)環(huán)境設置
- 04-02jquery與jsp,用jquery
- 01-10delphi制作wav文件的方法
- 01-11ajax實現(xiàn)頁面的局部加載