網(wǎng)站首頁 編程語言 正文
一.受限協(xié)程作用域
在協(xié)程的基礎(chǔ)與使用中提到,可以通過sequence方法構(gòu)建一個序列發(fā)生器。但當(dāng)在sequence方法中調(diào)用除了yield方法與yieldAll方法以外的其他掛起方法時,就會報錯。比如在sequence方法中調(diào)用delay方法,就會產(chǎn)生下面的報錯提示:
翻譯過來大致是“受限的掛起方法只能調(diào)用自身受限的協(xié)程作用域內(nèi)的成員變量或掛起方法。這是什么意思呢?
1.sequence方法
sequence方法就是構(gòu)建序列發(fā)生器用到的方法,內(nèi)部通過Sequence方法實現(xiàn),代碼如下:
@SinceKotlin("1.3")
public fun <T> sequence(@BuilderInference block: suspend SequenceScope<T>.() -> Unit): Sequence<T> = Sequence { iterator(block) }
其中參數(shù)block是一個在SequenceScope環(huán)境下的lambda表達式。
2.SequenceScope類
// 注意
@RestrictsSuspension
@SinceKotlin("1.3")
public abstract class SequenceScope<in T> internal constructor() {
// 向迭代器中提供一個數(shù)值
public abstract suspend fun yield(value: T)
// 向迭代器中提供一組數(shù)值
public abstract suspend fun yieldAll(iterator: Iterator<T>)
// 向迭代器中提供Collection類型的一組數(shù)值
public suspend fun yieldAll(elements: Iterable<T>) {
if (elements is Collection && elements.isEmpty()) return
return yieldAll(elements.iterator())
}
// 向迭代器中提供Sequence類型的一組數(shù)值
public suspend fun yieldAll(sequence: Sequence<T>) = yieldAll(sequence.iterator())
}
SequenceScope類是一個獨立的抽象類,沒有繼承任何的類。它提供了四個方法,只要都是用來向外提供數(shù)值或?qū)ο蟆6擃惓蔀槭芟迏f(xié)程作用域的關(guān)鍵在于該類被RestrictsSuspension注解修飾,代碼如下:
@SinceKotlin("1.3")
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
public annotation class RestrictsSuspension
RestrictsSuspension注解用于修飾一個類或接口,表示該類是受限的。在被該注解修飾的類的擴展掛起方法中,只能調(diào)用該注解修飾的類中定義的掛起方法,不能調(diào)用其他類的掛起方法。
具體的,在sequence方法中,block就是SequenceScope類的擴展方法,因此在block中,只能使用SequenceScope類中提供的掛起方法——yield方法和yieldAll方法。同時,SequenceScope類的構(gòu)造器被internal修飾,無法在外部被繼承,因此也就無法定義其他的掛起方法。
為什么受限協(xié)程作用域不允許調(diào)用其他的掛起方法呢?
因為當(dāng)一個方法掛起協(xié)程時,會獲取協(xié)程的續(xù)體,同時協(xié)程需要等待方法執(zhí)行完畢后的回調(diào),這意味著會暴露協(xié)程的續(xù)體。可能會造成掛起協(xié)程執(zhí)行的不確定性。
二.序列發(fā)生器
1.Sequence接口
首先來分析一下Sequence接口,代碼如下:
public interface Sequence<out T> {
public operator fun iterator(): Iterator<T>
}
2.Sequence方法
在協(xié)程中,有一個與Sequence接口同名的方法,該方法用于返回一個實現(xiàn)了Sequence接口的對象,代碼如下:
@kotlin.internal.InlineOnly
public inline fun <T> Sequence(crossinline iterator: () -> Iterator<T>): Sequence<T> = object : Sequence<T> {
override fun iterator(): Iterator<T> = iterator()
}
Sequence方法返回了一個匿名對象,并通過參數(shù)中的lambda表達式iterator實現(xiàn)了接口中的iterator方法。
從sequence方法的代碼可以知道,用于構(gòu)建序列發(fā)生器的sequence方法內(nèi)部調(diào)用了Sequence方法,同時還調(diào)用了iterator方法,將返回的Iterator對象,作為Sequence方法的參數(shù)。
3.iterator方法
@SinceKotlin("1.3")
public fun <T> iterator(@BuilderInference block: suspend SequenceScope<T>.() -> Unit): Iterator<T> {
val iterator = SequenceBuilderIterator<T>()
iterator.nextStep = block.createCoroutineUnintercepted(receiver = iterator, completion = iterator)
return iterator
}
iterator方法內(nèi)部創(chuàng)建了一個SequenceBuilderIterator對象,并且通過createCoroutineUnintercepted方法創(chuàng)建了一個協(xié)程,保存到了SequenceBuilderIterator對象的nextStep變量中。可以發(fā)現(xiàn),序列發(fā)生器的核心實現(xiàn)都在SequenceBuilderIterator類中。
4.SequenceBuilderIterator類
SequenceBuilderIterator類是用于對序列發(fā)生器進行迭代,在該類的內(nèi)部對狀態(tài)進行了劃分,代碼如下:
private typealias State = Int
// 沒有要發(fā)射的數(shù)據(jù)
private const val State_NotReady: State = 0
private const val State_ManyNotReady: State = 1
// 有要發(fā)射的數(shù)據(jù)
private const val State_ManyReady: State = 2
private const val State_Ready: State = 3
// 數(shù)據(jù)全部發(fā)射完畢
private const val State_Done: State = 4
// 發(fā)射過程中出錯
private const val State_Failed: State = 5
狀態(tài)轉(zhuǎn)移圖如下:
迭代器的初始狀態(tài)為State_NotReady,由于首次發(fā)射沒有數(shù)據(jù),因此會進入State_Failed狀態(tài)。
State_Failed狀態(tài)會從序列發(fā)生器中獲取數(shù)據(jù),如果是通過yield方法獲取的數(shù)據(jù),則會進入State_Ready狀態(tài),如果是通過yieldAll方法獲取的數(shù)據(jù),則會進入State_ManyReady狀態(tài)。
當(dāng)從序列發(fā)生器中獲取數(shù)據(jù)時,如果是在State_ManyReady和State_Ready狀態(tài),則直接發(fā)射一個數(shù)據(jù),對應(yīng)的進入到State_ManyNotReady和State_NotReady狀態(tài)。如果是在State_ManyNotReady和State_NotReady狀態(tài),則會判斷是否有數(shù)據(jù),如果有數(shù)據(jù)則對應(yīng)進入到State_ManyReady和State_Ready狀態(tài)。如果沒有則進入到State_Failed狀態(tài),獲取數(shù)據(jù)。
當(dāng)序列發(fā)生器發(fā)射完畢時,會進入State_Done狀態(tài)。
接下來對SequenceBuilderIterator類進行分析。
1.SequenceBuilderIterator類的全局變量
SequenceBuilderIterator類繼承自SequenceScope類,實現(xiàn)了Iterator接口和Continuation接口。代碼如下:
private class SequenceBuilderIterator<T> : SequenceScope<T>(), Iterator<T>, Continuation<Unit> {
// 迭代器的狀態(tài)
private var state = State_NotReady
// 迭代器下一個要發(fā)送的值
private var nextValue: T? = null
// 用于保存yieldAll方法傳入的迭代器
private var nextIterator: Iterator<T>? = null
// 用于獲取下一個數(shù)據(jù)的續(xù)體
var nextStep: Continuation<Unit>? = null
...
// 空的上下文
override val context: CoroutineContext
get() = EmptyCoroutineContext
}
為什么SequenceBuilderIterator類的上下文是空的呢?
因為SequenceBuilderIterator類繼承了SequenceScope類,因此該類也是受限的,因此不允許在類的擴展方法中調(diào)用類內(nèi)以外的掛起方法。自然也就不能進行調(diào)度、攔截等操作,所以上下文為空。在協(xié)程中,受限協(xié)程的上下文一般都是空上下文。
2.yield方法與yieldAll方法
yield方法與yieldAll方法是SequenceScope類中定義的兩個方法,在SequenceBuilderIterator類中的實現(xiàn)如下:
// 發(fā)射一個數(shù)據(jù)
override suspend fun yield(value: T) {
// 保存數(shù)據(jù)到全局變量中
nextValue = value
// 修改狀態(tài)
state = State_Ready
// 掛起協(xié)程,獲取續(xù)體
return suspendCoroutineUninterceptedOrReturn { c ->
// 保存續(xù)體到全局變量中
nextStep = c
// 掛起
COROUTINE_SUSPENDED
}
}
// 發(fā)射多個數(shù)據(jù)
override suspend fun yieldAll(iterator: Iterator<T>) {
// 如果迭代器沒有數(shù)據(jù),則直接返回
if (!iterator.hasNext()) return
// 如果有數(shù)據(jù),則保存到全局變量
nextIterator = iterator
// 修改狀態(tài)
state = State_ManyReady
// 掛起協(xié)程,獲取續(xù)體
return suspendCoroutineUninterceptedOrReturn { c ->
// 保存續(xù)體到全局變量中
nextStep = c
// 掛起
COROUTINE_SUSPENDED
}
}
通過上面的代碼可以知道,yield方法和yieldAll方法主要做了三件事情,掛起協(xié)程、修改狀態(tài)、保存要發(fā)送的數(shù)據(jù)和續(xù)體。而yieldAll發(fā)射多個數(shù)據(jù)原理在于保存了參數(shù)中Iterator接口指向的對象,通過迭代器獲取數(shù)據(jù)。
3.hasNext方法
hasNext方法是Iterator接口中定義的方法,用于迭代時判斷是否還有數(shù)據(jù),代碼如下:
override fun hasNext(): Boolean {
// 循環(huán)
while (true) {
// 判斷狀態(tài)
when (state) {
// 剛通過yield方法發(fā)射數(shù)據(jù)
State_NotReady -> {}
// 剛通過yieldAll方法發(fā)射數(shù)據(jù)
State_ManyNotReady ->
// 如果迭代器中還有數(shù)據(jù)
if (nextIterator!!.hasNext()) {
// 修改狀態(tài),返回true
state = State_ManyReady
return true
} else {
// 沒有數(shù)據(jù),則置空,丟棄迭代器
nextIterator = null
}
// 如果序列發(fā)生器已經(jīng)發(fā)射完數(shù)據(jù),返回false
State_Done -> return false
// 如果有數(shù)據(jù),則直接返回true
State_Ready, State_ManyReady -> return true
// 其他狀態(tài),則拋出異常
else -> throw exceptionalState()
}
// 走到這里,說明需要去獲取下一個數(shù)據(jù)
// 修改狀態(tài)
state = State_Failed
// 獲取全局保存的續(xù)體
val step = nextStep!!
// 置空
nextStep = null
// 恢復(fù)序列發(fā)生器的執(zhí)行,直到遇到y(tǒng)ield方法或yieldAll方法掛起
step.resume(Unit)
}
}
// 異常狀態(tài)的處理
private fun exceptionalState(): Throwable = when (state) {
State_Done -> NoSuchElementException()
State_Failed -> IllegalStateException("Iterator has failed.")
else -> IllegalStateException("Unexpected state of the iterator: $state")
}
4.next方法
next方法也是Iterator接口中定義的方法,用于在迭代器中存在數(shù)據(jù)時獲取數(shù)據(jù),代碼如下:
override fun next(): T {
// 判斷狀態(tài)
when (state) {
// 如果當(dāng)前處于已經(jīng)發(fā)射完數(shù)據(jù)的狀態(tài),則判斷是否有數(shù)據(jù)
State_NotReady, State_ManyNotReady -> return nextNotReady()
// 如果通過yieldAll方法獲取到了數(shù)據(jù)
State_ManyReady -> {
// 修改狀態(tài)
state = State_ManyNotReady
// 通過迭代器獲取數(shù)據(jù)
return nextIterator!!.next()
}
// 如果通過yield方法獲取到了數(shù)據(jù)
State_Ready -> {
// 修改狀態(tài)
state = State_NotReady
// 獲取保存的數(shù)據(jù)并進行類型轉(zhuǎn)換
@Suppress("UNCHECKED_CAST")
val result = nextValue as T
// 全局變量置空
nextValue = null
// 返回數(shù)據(jù)
return result
}
// 其他情況,則拋出異常
else -> throw exceptionalState()
}
}
// 如果沒有數(shù)據(jù),則拋出異常,有數(shù)據(jù),則返回數(shù)據(jù)
private fun nextNotReady(): T {
if (!hasNext()) throw NoSuchElementException() else return next()
}
5.總結(jié)
當(dāng)使用序列發(fā)生器進行迭代時,首先會調(diào)用hasNext方法,hasNext方法會通過保存的續(xù)體,恢復(fù)序列發(fā)生器所在的協(xié)程繼續(xù)執(zhí)行,獲取下一次待發(fā)射的數(shù)據(jù)。如果獲取了到數(shù)據(jù),則會返回true,這樣之后通過next方法就可以獲取到對應(yīng)的數(shù)據(jù)。
當(dāng)序列發(fā)生器所在的協(xié)程在執(zhí)行中遇到y(tǒng)ield方法時,會發(fā)生掛起,同時將下一次待發(fā)射的數(shù)據(jù)保存起來。如果遇到的是yieldAll方法,則保存的是迭代器,下一次發(fā)射數(shù)據(jù)時會從迭代器中獲取。
原文鏈接:https://blog.csdn.net/LeeDuoZuiShuai/article/details/126323313
相關(guān)推薦
- 2022-07-26 protected修飾的構(gòu)造方法在不同包中的使用
- 2022-01-17 怎么去操作從后端請求過來的參數(shù)
- 2022-11-17 Go語言學(xué)習(xí)教程之反射的示例詳解_Golang
- 2022-04-16 WPF創(chuàng)建Prism應(yīng)用程序_實用技巧
- 2022-11-12 C語言楊氏矩陣查找算法實例講解_C 語言
- 2022-04-14 error: failed to push some refs to ‘http://git.tex
- 2022-07-01 Python數(shù)據(jù)可視化繪圖實例詳解_python
- 2022-04-06 如何用C#實現(xiàn)SAGA分布式事務(wù)_C#教程
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支