




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)
文檔簡介
第KotlinSelect協(xié)程多路復(fù)用的實現(xiàn)詳解目錄前言1.Select的引入多路數(shù)據(jù)的選擇串行執(zhí)行協(xié)程并行執(zhí)行同時監(jiān)聽多路結(jié)果Select閃亮登場2.Select的使用3.Invoke函數(shù)的妙用4.Select的原理5.Select注意事項
前言
協(xié)程通信三劍客:Channel、Select、Flow,本篇將會重點分析Select的使用及原理。
通過本篇文章,你將了解到:
Select的引入Select的使用Invoke函數(shù)的妙用Select的原理Select注意事項
1.Select的引入
多路數(shù)據(jù)的選擇
串行執(zhí)行
如今的二維碼識別應(yīng)用場景越來越廣了,早期應(yīng)用比較廣泛的識別SDK如zxing、zbar,它們各有各的特點,也存在識別不出來的情況,為了將兩者優(yōu)勢結(jié)合起來,我們想到的方法是同一份二維碼圖片分別給兩者進行識別。
如下:
//從zxing獲取二維碼信息
suspendfungetQrcodeInfoFromZxing(bitmap:Bitmap):String{
//模擬耗時
delay(2000)
return"I'mfish"
//從zbar獲取二維碼信息
suspendfungetQrcodeInfoFromZbar(bitmap:Bitmap):String{
delay(1000)
return"I'mfish"
funtestSelect(){
runBlocking{
varbitmap=null
varstarTime=System.currentTimeMillis()
varqrcoe1=getQrcodeInfoFromZxing(bitmap)
varqrcode2=getQrcodeInfoFromZbar(bitmap)
println("qrcode1=$qrcoe1qrcode2=$qrcode2useTime:${System.currentTimeMillis()-starTime}ms")
}
查看打印,最后花費的時間:
qrcode1=Imfishqrcode2=ImfishuseTime:3013ms
當(dāng)然這是串行的方式效率比較低,我們想到了用協(xié)程來優(yōu)化它。
協(xié)程并行執(zhí)行
如下:
funtestSelect1(){
varbitmap=null;
varstarTime=System.currentTimeMillis()
vardeferredZxing=GlobalScope.async{
getQrcodeInfoFromZxing(bitmap)
vardeferredZbar=GlobalScope.async{
getQrcodeInfoFromZbar(bitmap)
runBlocking{
//掛起等待識別結(jié)果
varqrcoe1=deferredZxing.await()
//掛起等待識別結(jié)果
varqrcode2=deferredZbar.await()
println("qrcode1=$qrcoe1qrcode2=$qrcode2useTime:${System.currentTimeMillis()-starTime}ms")
}
查看打印,最后花費的時間:
qrcode1=Imfishqrcode2=ImfishuseTime:2084ms
可以看出,花費時間明顯變少了。
與上個Demo相比,雖然識別過程是放在協(xié)程里并行執(zhí)行的,但是在等待識別結(jié)果卻是串行的。我們引入兩個識別庫的初衷是哪個識別快就用哪個的結(jié)果,為了達成這個目的,傳統(tǒng)的方式是:
同時監(jiān)聽并記錄識別結(jié)果的返回。
同時監(jiān)聽多路結(jié)果
如下:
funtestSelect2(){
varbitmap=null;
varstarTime=System.currentTimeMillis()
vardeferredZxing=GlobalScope.async{
getQrcodeInfoFromZxing(bitmap)
vardeferredZbar=GlobalScope.async{
getQrcodeInfoFromZbar(bitmap)
varisEnd=false
varresult:String=null
GlobalScope.launch{
if(!isEnd){
//沒有結(jié)束,則繼續(xù)識別
varresultTmp=deferredZxing.await()
if(!isEnd){
//識別沒有結(jié)束,說明自己是第一個返回結(jié)果的
result=resultTmp
println("zxingrecognizeokuseTime:${System.currentTimeMillis()-starTime}ms")
//標(biāo)記識別結(jié)束
isEnd=true
GlobalScope.launch{
if(!isEnd){
varresultTmp=deferredZbar.await()
if(!isEnd){
//識別沒有結(jié)束,說明自己是第一個返回結(jié)果的
result=resultTmp
println("zbarrecognizeokuseTime:${System.currentTimeMillis()-starTime}ms")
isEnd=true
//檢測是否有結(jié)果返回
runBlocking{
while(!isEnd){
delay(1)
println("recognizeresult:$result")
}
通過檢測isEnd標(biāo)記來判斷是否有某個模塊返回結(jié)果。
結(jié)果如下:
zbarrecognizeokuseTime:1070ms
recognizeresult:Imfish
由于模擬設(shè)定的zbar解析速度快,因此每次都是采納的是zbar的結(jié)果,所花費的時間大幅減少了,該結(jié)果符合預(yù)期。
Select閃亮登場
雖說上個Demo結(jié)果符合預(yù)期,但是多了很多額外的代碼、多引入了其它協(xié)程,并且需要子模塊對標(biāo)記進行賦值(對isEnd進行賦值),沒有達到解耦的目的。我們希望子模塊的任務(wù)是單一且閉環(huán)的,如果能在一個函數(shù)里統(tǒng)一檢測結(jié)果的返回就好了。
Select就是為了解決多路數(shù)據(jù)的選擇而生的。
來看看它是怎么解決該問題的:
funtestSelect3(){
varbitmap=null;
varstarTime=System.currentTimeMillis()
vardeferredZxing=GlobalScope.async{
getQrcodeInfoFromZxing(bitmap)
vardeferredZbar=GlobalScope.async{
getQrcodeInfoFromZbar(bitmap)
runBlocking{
//通過select監(jiān)聽zxing、zbar結(jié)果返回
varresult=selectString{
//監(jiān)聽zxing
deferredZxing.onAwait{value-
//value為deferredZxing識別的結(jié)果
"zxingresult$value"
//監(jiān)聽zbar
deferredZbar.onAwait{value-
"zbarresult$value"
//運行到此,說明已經(jīng)有結(jié)果返回
println("resultfrom$resultuseTime:${System.currentTimeMillis()-starTime}")
}
結(jié)果如下:
resultfromzbarresultImfishuseTime:1079
符合預(yù)期,同時可以看出:相比上個Demo,這樣寫簡潔了許多。
2.Select的使用
除了可以監(jiān)聽async的結(jié)果,Select還可以監(jiān)聽Channel的發(fā)送方/接收方數(shù)據(jù),我們以監(jiān)聽接收方數(shù)據(jù)為例:
funtestSelect4(){
runBlocking{
varbitmap=null;
varstarTime=System.currentTimeMillis()
varreceiveChannelZxing=produce{
//生產(chǎn)數(shù)據(jù)
varresult=getQrcodeInfoFromZxing(bitmap)
//發(fā)送數(shù)據(jù)
send(result)
varreceiveChannelZbar=produce{
varresult=getQrcodeInfoFromZbar(bitmap)
send(result)
varresult=selectString{
//監(jiān)聽是否有數(shù)據(jù)發(fā)送過來
receiveChannelZxing.onReceive{
value-"zxingresult$value"
receiveChannelZbar.onReceive{
value-"zbarresult$value"
println("resultfrom$resultuseTime:${System.currentTimeMillis()-starTime}")
}
結(jié)果如下:
resultfromzbarresultImfishuseTime:1028
不論是async還是Channel,Select都可以監(jiān)聽它們的數(shù)據(jù),從而形成多路復(fù)用的效果。
在監(jiān)聽協(xié)程里調(diào)用select表達式,表達式{}內(nèi)聲明需要監(jiān)聽的協(xié)程的數(shù)據(jù),對于select來說有兩種場景:
沒有數(shù)據(jù),則select掛起協(xié)程并等待直到其它協(xié)程數(shù)據(jù)準(zhǔn)備完成后再次恢復(fù)select所在的協(xié)程。有數(shù)據(jù),則select正常執(zhí)行并返回獲取的數(shù)據(jù)。
3.Invoke函數(shù)的妙用
在分析Select原理之前,需要弄明白invoke函數(shù)的原理。
對于Kotlin類來說,都可以重寫其invoke函數(shù)。
operatorfuninvoke():String{
return"I'mfish"
如上,重寫了SelectDemo里的invoke函數(shù),和普通成員函數(shù)一樣,我們可以通過對象調(diào)用它。
funmain(args:ArrayString){
varselectDemo=SelectDemo()
varresult=selectDemo.invoke()
println("result:$result")
當(dāng)然,可以進一步簡化:
funmain(args:ArrayString){
varselectDemo=SelectDemo()
varresult=selectDemo()
println("result:$result")
這里涉及到了kotlin的語法糖:對象居然可以像函數(shù)一樣調(diào)用。
作為函數(shù),invoke當(dāng)然也可以接收高階函數(shù)作為參數(shù):
operatorfuninvoke(block:(Int)-String):String{
returnblock(3)
funmain(args:ArrayString){
varselectDemo=SelectDemo()
varresult=selectDemo{age-
when(age){
3-"I'mfish3"
4-"I'mfish4"
else-"error"
println("result:$result")
因此,當(dāng)看到對象作為函數(shù)調(diào)用時,實際上調(diào)用的是invoke函數(shù),具體的邏輯需要查看其invoke函數(shù)的實現(xiàn)。
4.Select的原理
上篇分析過Channel,因此本篇趁熱打鐵,通過Select監(jiān)聽Channel數(shù)據(jù)的變化來分析其原理,為方便講解,我們先以監(jiān)聽一個Channel的為例。
先從select表達式本身入手。
funtestSelect5(){
runBlocking{
varstarTime=System.currentTimeMillis()
varreceiveChannelZxing=produce{
//發(fā)送數(shù)據(jù)
send("I'mfish")
//確保channel數(shù)據(jù)已經(jīng)send
delay(1000)
varresult=selectString{
//監(jiān)聽是否有數(shù)據(jù)發(fā)送過來
receiveChannelZxing.onReceive{value-
"zxingresult$value"
println("resultfrom$resultuseTime:${System.currentTimeMillis()-starTime}")
}
select是掛起函數(shù),因此協(xié)程運行到此有可能被掛起。
#Select.kt
publicsuspendinlinefunRselect(crossinlinebuilder:SelectBuilderR.()-Unit):R{
//...
returnsuspendCoroutineUninterceptedOrReturn{uCont-
//傳入父協(xié)程體
valscope=SelectBuilderImpl(uCont)
try{
//執(zhí)行builder
builder(scope)
}catch(e:Throwable){
scope.handleBuilderException(e)
//通過返回值判斷是否需要掛起協(xié)程
scope.getResult()
}
重點看builder(scope),builder是高階函數(shù),實際上就是執(zhí)行了select花括號里的內(nèi)容,而它里面就是監(jiān)聽數(shù)據(jù)是否返回。
receiveChannelZxing.onReceive
剛開始看的時候勢必以為onReceive是個函數(shù),然而它是ReceiveChannel里的成員變量:
#Channel.kt
publicvalonReceive:SelectClause1E
通過上一節(jié)的分析可知,關(guān)鍵是要找到SelectClause1的invoke的實現(xiàn)。
#Select.kt
publicinterfaceSelectBuilderinR{
//block有個入?yún)?/p>
//聲明了SelectClause1的擴展函數(shù)invoke
publicoperatorfunQSelectClause1Q.invoke(block:suspend(Q)-R)
overridefunQSelectClause1Q.invoke(block:suspend(Q)-R){
//SelectBuilderImpl實現(xiàn)了SelectClause1的invoke函數(shù)
registerSelectClause1(this@SelectBuilderImpl,block)
}
再看onReceive的賦值:
#AbstractChannel.kt
finaloverridevalonReceive:SelectClause1E
get()=object:SelectClause1E{
@Suppress("UNCHECKED_CAST")
overridefunRregisterSelectClause1(select:SelectInstanceR,block:suspend(E)-R){
registerSelectReceiveMode(select,RECEIVE_THROWS_ON_CLOSE,blockassuspend(Any)-R)
因此,簡單總結(jié)調(diào)用棧如下:
當(dāng)調(diào)用receiveChannelZxing.onReceive{},實際上調(diào)用了SelectClause1.invoke(),而它里面又調(diào)用了SelectClause1.registerSelectClause1(),最終調(diào)用了AbstractChannel.registerSelectReceiveMode。
AbstractChannel.registerSelectReceiveMode
#AbstractChannel.kt
privatefunRregisterSelectReceiveMode(select:SelectInstanceR,receiveMode:Int,block:suspend(Any)-R){
while(true){
//如果已經(jīng)有結(jié)果了,則直接返回-------①
if(select.isSelected)return
if(isEmptyImpl){
//沒有發(fā)送者在等待,則入隊等待,并返回-------②
if(enqueueReceiveSelect(select,block,receiveMode))return
}else{
//直接取出值-------③
valpollResult=pollSelectInternal(select)
when{
pollResult===ALREADY_SELECTED-return
pollResult===POLL_FAILED-{}//retry
pollResult===RETRY_ATOMIC-{}//retry
//調(diào)用block-------④
else-block.tryStartBlockUnintercepted(select,receiveMode,pollResult)
}
分為4個點,接著來一一分析。
①select同時監(jiān)聽多個值,若是有1個符合要求的數(shù)據(jù)返回了,那么該isSelected標(biāo)記為true,當(dāng)檢測到該標(biāo)記為true時直接退出。
結(jié)合之前的Demo,zbar已經(jīng)識別出結(jié)果了,當(dāng)select檢測zxing的結(jié)果時直接返回。
②:
#AbstractChannel.kt
privatefunRenqueueReceiveSelect(
select:SelectInstanceR,
block:suspend(Any)-R,
receiveMode:Int
):Boolean{
//構(gòu)造為Node元素
valnode=AbstractChannel.ReceiveSelect(this,select,block,receiveMode)
//添加到Channel隊列里
valresult=enqueueReceive(node)
if(result)select.disposeOnSelect(node)
returnresult
}
當(dāng)select時,發(fā)現(xiàn)Channel里沒有數(shù)據(jù),說明Channel還沒有開始send,因此構(gòu)造了Node(ReceiveSele
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 中英文商品合同范例
- 公司標(biāo)準(zhǔn)合同范例作用
- 買賣土礦合同范例
- 2000用工合同范例
- 企業(yè)機租賃合同范例
- 伊利雞蛋采購合同范例
- 個人代開銷售合同范例
- 保證借款標(biāo)準(zhǔn)合同范例
- 房產(chǎn)信息登記及抵押貸款服務(wù)合同
- 網(wǎng)絡(luò)信息審核辦公室租賃與專業(yè)設(shè)備采購合同
- 人工智能引論智慧樹知到課后章節(jié)答案2023年下浙江大學(xué)
- 小班數(shù)學(xué)《圖形食品品嘗會》
- 12 黑板報(教案) 贛美版美術(shù)三年級下冊
- 大學(xué)英語六級經(jīng)典必背500句
- 2022神經(jīng)外科手術(shù)分級目錄
- 管理系統(tǒng)中計算機應(yīng)用詳細課件
- 房建項目檢驗和試驗計劃
- 脊髓損傷患者的坐起坐位及坐位平衡訓(xùn)練
- 夯實基礎(chǔ)總結(jié)反思課件高三學(xué)習(xí)主題班會
- 《上海市奉賢區(qū)小區(qū)機動車停放管理工作調(diào)查報告》4300字
- 湖南裕能招股說明書梳理
評論
0/150
提交評論