C#中 Thread,Task,Async/Await,IAsyncResult 的那些事兒!
當(dāng)前位置:點(diǎn)晴教程→知識(shí)管理交流
→『 技術(shù)文檔交流 』
說(shuō)起異步,Thread,Task,async/await,IAsyncResult 這些東西肯定是繞不開(kāi)的,今天就來(lái)依次聊聊他們 1.線(xiàn)程(Thread)多線(xiàn)程的意義在于一個(gè)應(yīng)用程序中,有多個(gè)執(zhí)行部分可以同時(shí)執(zhí)行;對(duì)于比較耗時(shí)的操作(例如io,數(shù)據(jù)庫(kù)操作),或者等待響應(yīng)(如WCF通信)的操作,可以單獨(dú)開(kāi)啟后臺(tái)線(xiàn)程來(lái)執(zhí)行,這樣主線(xiàn)程就不會(huì)阻塞,可以繼續(xù)往下執(zhí)行;等到后臺(tái)線(xiàn)程執(zhí)行完畢,再通知主線(xiàn)程,然后做出對(duì)應(yīng)操作! 在C#中開(kāi)啟新線(xiàn)程比較簡(jiǎn)單 static void Main(string[] args) { Console.WriteLine("主線(xiàn)程開(kāi)始"); //IsBackground=true,將其設(shè)置為后臺(tái)線(xiàn)程 Thread t = new Thread(Run) { IsBackground = true }; t.Start(); //主線(xiàn)程結(jié)束,后臺(tái)線(xiàn)程會(huì)自動(dòng)結(jié)束,不管有沒(méi)有執(zhí)行完成 //Thread.Sleep(300); Thread.Sleep(1500); Console.WriteLine("主線(xiàn)程結(jié)束"); }
static void Run() { Thread.Sleep(700); Console.WriteLine("這是后臺(tái)線(xiàn)程調(diào)用"); } 執(zhí)行結(jié)果如下圖, 可以看到在啟動(dòng)后臺(tái)線(xiàn)程之后,主線(xiàn)程繼續(xù)往下執(zhí)行了,并沒(méi)有等到后臺(tái)線(xiàn)程執(zhí)行完之后。 1.1 線(xiàn)程池試想一下,如果有大量的任務(wù)需要處理,例如網(wǎng)站后臺(tái)對(duì)于HTTP請(qǐng)求的處理,那是不是要對(duì)每一個(gè)請(qǐng)求創(chuàng)建一個(gè)后臺(tái)線(xiàn)程呢?顯然不合適,這會(huì)占用大量?jī)?nèi)存,而且頻繁地創(chuàng)建的過(guò)程也會(huì)嚴(yán)重影響速度,那怎么辦呢?線(xiàn)程池就是為了解決這一問(wèn)題,把創(chuàng)建的線(xiàn)程存起來(lái),形成一個(gè)線(xiàn)程池(里面有多個(gè)線(xiàn)程),當(dāng)要處理任務(wù)時(shí),若線(xiàn)程池中有空閑線(xiàn)程(前一個(gè)任務(wù)執(zhí)行完成后,線(xiàn)程不會(huì)被回收,會(huì)被設(shè)置為空閑狀態(tài)),則直接調(diào)用線(xiàn)程池中的線(xiàn)程執(zhí)行(例asp.net處理機(jī)制中的Application對(duì)象), 使用事例: for (int i = 0; i < 10; i++) { ThreadPool.QueueUserWorkItem(m => { Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString()); }); } Console.Read(); 運(yùn)行結(jié)果: 可以看到,雖然執(zhí)行了10次,但并沒(méi)有創(chuàng)建10個(gè)線(xiàn)程。 1.2 信號(hào)量(Semaphore)Semaphore負(fù)責(zé)協(xié)調(diào)線(xiàn)程,可以限制對(duì)某一資源訪問(wèn)的線(xiàn)程數(shù)量 這里對(duì)SemaphoreSlim類(lèi)的用法做一個(gè)簡(jiǎn)單的事例: static SemaphoreSlim semLim = new SemaphoreSlim(3); //3表示最多只能有三個(gè)線(xiàn)程同時(shí)訪問(wèn) 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("線(xiàn)程" + Thread.CurrentThread.ManagedThreadId.ToString() + "開(kāi)始執(zhí)行"); Thread.Sleep(2000); Console.WriteLine("線(xiàn)程" + Thread.CurrentThread.ManagedThreadId.ToString() + "執(zhí)行完畢"); semLim.Release(); } 執(zhí)行結(jié)果如下: 可以看到,剛開(kāi)始只有三個(gè)線(xiàn)程在執(zhí)行,當(dāng)一個(gè)線(xiàn)程執(zhí)行完畢并釋放之后,才會(huì)有新的線(xiàn)程來(lái)執(zhí)行方法! 除了SemaphoreSlim類(lèi),還可以使用Semaphore類(lèi),感覺(jué)更加靈活,感興趣的話(huà)可以搜一下,這里就不做演示了! 2.TaskTask是.NET4.0加入的,跟線(xiàn)程池ThreadPool的功能類(lèi)似,用Task開(kāi)啟新任務(wù)時(shí),會(huì)從線(xiàn)程池中調(diào)用線(xiàn)程,而Thread每次實(shí)例化都會(huì)創(chuàng)建一個(gè)新的線(xiàn)程。 Console.WriteLine("主線(xiàn)程啟動(dòng)"); //Task.Run啟動(dòng)一個(gè)線(xiàn)程 //Task啟動(dòng)的是后臺(tái)線(xiàn)程,要在主線(xiàn)程中等待后臺(tái)線(xiàn)程執(zhí)行完畢,可以調(diào)用Wait方法 Task task = Task.Factory.StartNew(() => { Thread.Sleep(1500); Console.WriteLine("task啟動(dòng)"); }); Task task = Task.Run(() => { Thread.Sleep(1500); Console.WriteLine("task啟動(dòng)"); }); Thread.Sleep(300); task.Wait(); Console.WriteLine("主線(xiàn)程結(jié)束"); 執(zhí)行結(jié)果如下: 開(kāi)啟新任務(wù)的方法:Task.Run()或者Task.Factory.StartNew(),開(kāi)啟的是后臺(tái)線(xiàn)程 要在主線(xiàn)程中等待后臺(tái)線(xiàn)程執(zhí)行完畢,可以使用Wait方法(會(huì)以同步的方式來(lái)執(zhí)行)。不用Wait則會(huì)以異步的方式來(lái)執(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調(diào)用的Thread Id =" + Thread.CurrentThread.ManagedThreadId); } 執(zhí)行結(jié)果: 可以看出來(lái),直接用Thread會(huì)開(kāi)啟5個(gè)線(xiàn)程,用Task(用了線(xiàn)程池)開(kāi)啟了3個(gè)! 2.1 Task<TResult>Task<TResult>就是有返回值的Task,TResult就是返回值類(lèi)型。 Console.WriteLine("主線(xiàn)程開(kāi)始"); //返回值類(lèi)型為string Task<string> task = Task<string>.Run(() => { Thread.Sleep(2000); return Thread.CurrentThread.ManagedThreadId.ToString(); }); //會(huì)等到task執(zhí)行完畢才會(huì)輸出; Console.WriteLine(task.Result); Console.WriteLine("主線(xiàn)程結(jié)束"); 運(yùn)行結(jié)果: 通過(guò)task.Result可以取到返回值,若取值的時(shí)候,后臺(tái)線(xiàn)程還沒(méi)執(zhí)行完,則會(huì)等待其執(zhí)行完畢! 簡(jiǎn)單提一下: Task任務(wù)可以通過(guò)CancellationTokenSource類(lèi)來(lái)取消,感覺(jué)用得不多,用法比較簡(jiǎn)單,感興趣的話(huà)可以搜一下! 3. async/awaitasync/await是C#5.0中推出的,先上用法: static void Main(string[] args) { Console.WriteLine("-------主線(xiàn)程啟動(dòng)-------"); Task<int> task = GetStrLengthAsync(); Console.WriteLine("主線(xiàn)程繼續(xù)執(zhí)行"); Console.WriteLine("Task返回的值" + task.Result); Console.WriteLine("-------主線(xiàn)程結(jié)束-------"); } static async Task<int> GetStrLengthAsync() { Console.WriteLine("GetStrLengthAsync方法開(kāi)始執(zhí)行"); //此處返回的<string>中的字符串類(lèi)型,而不是Task<string> string str = await GetString(); Console.WriteLine("GetStrLengthAsync方法執(zhí)行結(jié)束"); return str.Length; } static Task<string> GetString() { //Console.WriteLine("GetString方法開(kāi)始執(zhí)行") return Task<string>.Run(() => { Thread.Sleep(2000); return "GetString的返回值"; }); } async用來(lái)修飾方法,表明這個(gè)方法是異步的,聲明的方法的返回類(lèi)型必須為:void,Task或Task<TResult>。 await必須用來(lái)修飾Task或Task<TResult>,而且只能出現(xiàn)在已經(jīng)用async關(guān)鍵字修飾的異步方法中。通常情況下,async/await成對(duì)出現(xiàn)才有意義, 看看運(yùn)行結(jié)果: 可以看出來(lái),main函數(shù)調(diào)用GetStrLengthAsync方法后,在await之前,都是同步執(zhí)行的,直到遇到await關(guān)鍵字,main函數(shù)才返回繼續(xù)執(zhí)行。 那么是否是在遇到await關(guān)鍵字的時(shí)候程序自動(dòng)開(kāi)啟了一個(gè)后臺(tái)線(xiàn)程去執(zhí)行GetString方法呢? 現(xiàn)在把GetString方法中的那行注釋加上,運(yùn)行的結(jié)果是: 大家可以看到,在遇到await關(guān)鍵字后,沒(méi)有繼續(xù)執(zhí)行GetStrLengthAsync方法后面的操作,也沒(méi)有馬上反回到main函數(shù)中,而是執(zhí)行了GetString的第一行,以此可以判斷await這里并沒(méi)有開(kāi)啟新的線(xiàn)程去執(zhí)行GetString方法,而是以同步的方式讓GetString方法執(zhí)行,等到執(zhí)行到GetString方法中的Task<string>.Run()的時(shí)候才由Task開(kāi)啟了后臺(tái)線(xiàn)程! 那么await的作用是什么呢? 可以從字面上理解,上面提到task.wait可以讓主線(xiàn)程等待后臺(tái)線(xiàn)程執(zhí)行完畢,await和wait類(lèi)似,同樣是等待,等待Task<string>.Run()開(kāi)始的后臺(tái)線(xiàn)程執(zhí)行完畢,不同的是await不會(huì)阻塞主線(xiàn)程,只會(huì)讓GetStrLengthAsync方法暫停執(zhí)行。 那么await是怎么做到的呢?有沒(méi)有開(kāi)啟新線(xiàn)程去等待? 只有兩個(gè)線(xiàn)程(主線(xiàn)程和Task開(kāi)啟的線(xiàn)程)!至于怎么做到的(我也不知道......>_<),大家有興趣的話(huà)研究下吧! 4.IAsyncResultIAsyncResult自.NET1.1起就有了,包含可異步操作的方法的類(lèi)需要實(shí)現(xiàn)它,Task類(lèi)就實(shí)現(xiàn)了該接口 在不借助于Task的情況下怎么實(shí)現(xiàn)異步呢? class Program { static void Main(string[] args) { Console.WriteLine("主程序開(kāi)始--------------------"); 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("主線(xiàn)程線(xiàn)程 {0} 正在運(yùn)行.", Thread.CurrentThread.ManagedThreadId) //會(huì)阻塞線(xiàn)程,直到后臺(tái)線(xiàn)程執(zhí)行完畢之后,才會(huì)往下執(zhí)行 result.AsyncWaitHandle.WaitOne(); Console.WriteLine("主程序在做一些事情!!!"); //獲取異步執(zhí)行的結(jié)果 string returnValue = caller.EndInvoke(out threadId, result); //釋放資源 result.AsyncWaitHandle.Close(); Console.WriteLine("主程序結(jié)束--------------------"); Console.Read(); } } public class AsyncDemo { //供后臺(tái)線(xiàn)程執(zhí)行的方法 public string TestMethod(int callDuration, out int threadId) { Console.WriteLine("測(cè)試方法開(kāi)始執(zhí)行."); Thread.Sleep(callDuration); threadId = Thread.CurrentThread.ManagedThreadId; return String.Format("測(cè)試方法執(zhí)行的時(shí)間 {0}.", callDuration.ToString()); } } public delegate string AsyncMethodCaller(int callDuration, out int threadId); 關(guān)鍵步驟就是紅色字體的部分,運(yùn)行結(jié)果: 和Task的用法差異不是很大!result.AsyncWaitHandle.WaitOne()就類(lèi)似Task的Wait。 5.Parallel最后說(shuō)一下在循環(huán)中開(kāi)啟多線(xiàn)程的簡(jiǎ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(); //會(huì)調(diào)用線(xiàn)程池中的線(xiàn)程 Parallel.For(1, 11, i =>{ Console.WriteLine(i + ",線(xiàn)程ID:" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(1000);}); watch2.Stop(); Console.WriteLine(watch2.Elapsed); 運(yùn)行結(jié)果: 循環(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); 6.異步的回調(diào)文中所有Task<TResult>的返回值都是直接用task.result獲取,這樣如果后臺(tái)任務(wù)沒(méi)有執(zhí)行完畢的話(huà),主線(xiàn)程會(huì)等待其執(zhí)行完畢,這樣的話(huà)就和同步一樣了(看上去一樣,但其實(shí)await的時(shí)候并不會(huì)造成線(xiàn)程的阻塞,web程序感覺(jué)不到,但是wpf,winform這樣的桌面程序若不使用異步,會(huì)造成UI線(xiàn)程的阻塞)。簡(jiǎn)單演示一下Task回調(diào)函數(shù)的使用: Console.WriteLine("主線(xiàn)程開(kāi)始"); Task<string> task = Task<string>.Run(() => { Thread.Sleep(2000); return Thread.CurrentThread.ManagedThreadId.ToString(); }); //會(huì)等到任務(wù)執(zhí)行完之后執(zhí)行 task.GetAwaiter().OnCompleted(() =>{ Console.WriteLine(task.Result);}); Console.WriteLine("主線(xiàn)程結(jié)束"); Console.Read(); 執(zhí)行結(jié)果: OnCompleted中的代碼會(huì)在任務(wù)執(zhí)行完成之后執(zhí)行! 另外task.ContinueWith()也是一個(gè)重要的方法: Console.WriteLine("主線(xiàn)程開(kāi)始"); Task<string> task = Task<string>.Run(() => { Thread.Sleep(2000); return Thread.CurrentThread.ManagedThreadId.ToString(); }); task.GetAwaiter().OnCompleted(() =>{ Console.WriteLine(task.Result); }); task.ContinueWith(m=>{ Console.WriteLine("第一個(gè)任務(wù)結(jié)束啦!我是第二個(gè)任務(wù)"); }); Console.WriteLine("主線(xiàn)程結(jié)束"); Console.Read(); 執(zhí)行結(jié)果: ContinueWith()方法可以讓該后臺(tái)線(xiàn)程繼續(xù)執(zhí)行新的任務(wù)。 Task的使用還是比較靈活的,大家可以研究下,好了,以上就是全部?jī)?nèi)容了,篇幅和能力都有限,希望對(duì)大家有用!
出處:http://www.hzhcontrols.com/ 該文章在 2024/1/3 23:59:27 編輯過(guò) |
關(guān)鍵字查詢(xún)
相關(guān)文章
正在查詢(xún)... |