說(shuō)明:本文只討論同步異步,以及異步的多線(xiàn)程實(shí)現(xiàn),不討論多線(xiàn)程導(dǎo)致的數(shù)據(jù)不同步問(wèn)題.
同步和異步是相對(duì)的 ?
在異步概念出來(lái)之前造虏,我們的代碼都是按同步的方式寫(xiě)的。簡(jiǎn)單來(lái)說(shuō)梁沧,就是程序嚴(yán)格按照代碼的邏輯次序起惕,一行一行執(zhí)行。一行代碼不走完不會(huì)執(zhí)行下一行代碼,當(dāng)一行代碼 特別耗時(shí)時(shí)就會(huì)造成阻塞,等待,造成的現(xiàn)象就是卡死.
異步就是為了解決這種阻塞而產(chǎn)生的,它是一種功能要求,說(shuō)異步是功能要求意味著它的實(shí)現(xiàn)可以有多種形式,只要解決阻塞的問(wèn)題就可以了.
舉例:
1.異步 I/O操作
I/O操作非常耗時(shí),但是在硬件層面上 硬盤(pán),顯卡和內(nèi)存是可以在不耗費(fèi)CPU資源的情況下進(jìn)行數(shù)據(jù)交換的.所以CPU可以控制硬盤(pán)和內(nèi)存之間的開(kāi)啟,且不用等待,當(dāng)資源交換完畢后回調(diào)給CPU就可以了 ,這就實(shí)現(xiàn)了異步,I/O流的異步操作是最常見(jiàn)的異步操作之一.
2.Unity中的協(xié)程(異步的單線(xiàn)程實(shí)現(xiàn))
Unity中的Coroutine旨在將一個(gè)函數(shù)分多次執(zhí)行,從而實(shí)現(xiàn)等同并發(fā)的處理方式,Coroutine可以用來(lái)實(shí)現(xiàn)異步,比如計(jì)時(shí)器(WaitForSeconds()),此外繼承CustomYieldInsruction可以實(shí)現(xiàn)更豐富的異步效果.
3.異步不是多線(xiàn)程
前面說(shuō)到異步是一種功能要求,并且舉例了I/O流的異步和Unity協(xié)程的異步,I/O流的異步是硬件屬性實(shí)現(xiàn)的異步,Unity協(xié)程則是在主線(xiàn)程中對(duì)給定條件的判斷 決定是否執(zhí)行下一行代碼,而多線(xiàn)程則是另外一種異步的實(shí)現(xiàn)方式,因?yàn)楫惒胶投嗑€(xiàn)程總是被同時(shí)提到,且有迷惑性,所以又強(qiáng)調(diào)了下這個(gè)問(wèn)題.
C#中使用多線(xiàn)程實(shí)現(xiàn)異步
1.用C#語(yǔ)言提供的多線(xiàn)程實(shí)現(xiàn)異步,這里使用Task來(lái)實(shí)現(xiàn),主要使用await 和async. (主要原因是老版本的.net 多線(xiàn)程不太好懂,最初需要自己申請(qǐng)Thread,后來(lái)衍生出ThradPool允許開(kāi)發(fā)者申請(qǐng)線(xiàn)程池,但是還不夠好用).
Thread.Sleep()用來(lái)模擬耗時(shí)操作
Thread.CurrentThread.ManagedThreadId表示CLR提供的虛擬托管線(xiàn)程Id(用來(lái)判斷當(dāng)前方法在哪個(gè)線(xiàn)程被執(zhí)行)
先實(shí)現(xiàn)一下同步吧(不是說(shuō)大家不會(huì)哈)....用作和下文異步的對(duì)比
結(jié)果為:
異步的多線(xiàn)程實(shí)現(xiàn):
這里需要解釋下await關(guān)鍵字和async關(guān)鍵字
async用來(lái)修飾 方法,表示該方法可能會(huì)采取異步的方式運(yùn)行,修飾的方法必須void 或返回Task,Task<T>
await用來(lái)修飾 Task,Task<T> ,當(dāng)程序運(yùn)行遇到await時(shí)會(huì)跳出當(dāng)前方法繼續(xù)執(zhí)行主線(xiàn)程中的方法,并安排持續(xù)監(jiān)聽(tīng)它修飾的Task是否生成了結(jié)果,一旦生成結(jié)果,則再調(diào)回來(lái)繼續(xù)執(zhí)行修飾的Task下一行的代碼.并且可以用Task.result訪問(wèn)到它的結(jié)果
程序進(jìn)入await修飾的Task中后,如果方法中不存在await 則當(dāng)前是在同步執(zhí)行,執(zhí)行完該方法時(shí),接著await后面執(zhí)行. 如果修飾的Task中存在await 則在當(dāng)前線(xiàn)程中又申請(qǐng)了新的線(xiàn)程處理await后面的Task,這是一個(gè)循環(huán)的過(guò)程.
await修飾的Task中沒(méi)有await 則當(dāng)前執(zhí)行的是同步方法反之 await修飾的task是個(gè)異步Task ,則它就是異步方法 ,那么如何聲明一個(gè)異步的Task呢?
聲明一個(gè)異步的Task:
Task.Run();(多線(xiàn)程編程中有很多方法聲明異步Task,這里就放個(gè)最快實(shí)現(xiàn)的)
Task.Run的內(nèi)部代碼會(huì)占用線(xiàn)程池資源钓葫,并在一個(gè)可用的線(xiàn)程上與主線(xiàn)程并行運(yùn)行。
該代碼每個(gè)async修飾的方法中都有await,遇到await就跳出,task執(zhí)行完畢就回來(lái),所以Console.WriteLine(${“退出第{index}個(gè)方法”})一定是一個(gè)AsyncFunction中最后一個(gè)執(zhí)行的.結(jié)果為
若將上文的SimulateLongTimeFunction (index);?await修飾去掉 則???Console.WriteLine($"退出第{index}個(gè)方法");是for循環(huán)中每一輪結(jié)束的最后一句打印
如下圖:
說(shuō)完了await就要提到另一個(gè)詞wait ,二者特別容易混淆,所以我想說(shuō)完同步異步和await之后再提wait的事 ?上文中模擬的耗時(shí)任務(wù)總是最后才完成.假如我要使用他們的結(jié)果,就必須等待他們都處理完畢 就可以用Task.Wait來(lái)等待結(jié)果,該方法是一個(gè)同步方法 ,所以它是阻塞的.
多線(xiàn)程編程會(huì)帶來(lái)數(shù)據(jù)沖突的問(wèn)題,盡管可以通過(guò)lock的方式避免數(shù)據(jù)沖突 但是,lock開(kāi)銷(xiāo)比較大.
2.Unity開(kāi)發(fā)中還可使用Unity提供的JobSystem實(shí)現(xiàn)異步
很多功能C#實(shí)現(xiàn)一次,Unity在其基礎(chǔ)上結(jié)合自己的使用場(chǎng)景又封裝一次這種操作還蠻常見(jiàn)的,比如C#的event和Unity的unityEvent等等.JobSystem的最小單位是Job,嗯聽(tīng)起來(lái)和Task差不多...
Unity號(hào)稱(chēng)JobSystem幫開(kāi)發(fā)者解決了很多痛點(diǎn),比如前面說(shuō)到的多線(xiàn)程數(shù)據(jù)沖突的問(wèn)題? 我也沒(méi)細(xì)研究過(guò)...