您好,登錄后才能下訂單哦!
本篇內容主要講解“Android協程作用域與序列發生器限制是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Android協程作用域與序列發生器限制是什么”吧!
在協程的基礎與使用中提到,可以通過sequence方法構建一個序列發生器。但當在sequence方法中調用除了yield方法與yieldAll方法以外的其他掛起方法時,就會報錯。比如在sequence方法中調用delay方法,就會產生下面的報錯提示:
翻譯過來大致是“受限的掛起方法只能調用自身受限的協程作用域內的成員變量或掛起方法。這是什么意思呢?
sequence方法就是構建序列發生器用到的方法,內部通過Sequence方法實現,代碼如下:
@SinceKotlin("1.3") public fun <T> sequence(@BuilderInference block: suspend SequenceScope<T>.() -> Unit): Sequence<T> = Sequence { iterator(block) }
其中參數block是一個在SequenceScope環境下的lambda表達式。
// 注意 @RestrictsSuspension @SinceKotlin("1.3") public abstract class SequenceScope<in T> internal constructor() { // 向迭代器中提供一個數值 public abstract suspend fun yield(value: T) // 向迭代器中提供一組數值 public abstract suspend fun yieldAll(iterator: Iterator<T>) // 向迭代器中提供Collection類型的一組數值 public suspend fun yieldAll(elements: Iterable<T>) { if (elements is Collection && elements.isEmpty()) return return yieldAll(elements.iterator()) } // 向迭代器中提供Sequence類型的一組數值 public suspend fun yieldAll(sequence: Sequence<T>) = yieldAll(sequence.iterator()) }
SequenceScope類是一個獨立的抽象類,沒有繼承任何的類。它提供了四個方法,只要都是用來向外提供數值或對象。而該類成為受限協程作用域的關鍵在于該類被RestrictsSuspension注解修飾,代碼如下:
@SinceKotlin("1.3") @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) public annotation class RestrictsSuspension
RestrictsSuspension注解用于修飾一個類或接口,表示該類是受限的。在被該注解修飾的類的擴展掛起方法中,只能調用該注解修飾的類中定義的掛起方法,不能調用其他類的掛起方法。
具體的,在sequence方法中,block就是SequenceScope類的擴展方法,因此在block中,只能使用SequenceScope類中提供的掛起方法——yield方法和yieldAll方法。同時,SequenceScope類的構造器被internal修飾,無法在外部被繼承,因此也就無法定義其他的掛起方法。
為什么受限協程作用域不允許調用其他的掛起方法呢?
因為當一個方法掛起協程時,會獲取協程的續體,同時協程需要等待方法執行完畢后的回調,這意味著會暴露協程的續體。可能會造成掛起協程執行的不確定性。
首先來分析一下Sequence接口,代碼如下:
public interface Sequence<out T> { public operator fun iterator(): Iterator<T> }
在協程中,有一個與Sequence接口同名的方法,該方法用于返回一個實現了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方法返回了一個匿名對象,并通過參數中的lambda表達式iterator實現了接口中的iterator方法。
從sequence方法的代碼可以知道,用于構建序列發生器的sequence方法內部調用了Sequence方法,同時還調用了iterator方法,將返回的Iterator對象,作為Sequence方法的參數。
@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方法內部創建了一個SequenceBuilderIterator對象,并且通過createCoroutineUnintercepted方法創建了一個協程,保存到了SequenceBuilderIterator對象的nextStep變量中。可以發現,序列發生器的核心實現都在SequenceBuilderIterator類中。
SequenceBuilderIterator類是用于對序列發生器進行迭代,在該類的內部對狀態進行了劃分,代碼如下:
private typealias State = Int // 沒有要發射的數據 private const val State_NotReady: State = 0 private const val State_ManyNotReady: State = 1 // 有要發射的數據 private const val State_ManyReady: State = 2 private const val State_Ready: State = 3 // 數據全部發射完畢 private const val State_Done: State = 4 // 發射過程中出錯 private const val State_Failed: State = 5
狀態轉移圖如下:
迭代器的初始狀態為State_NotReady,由于首次發射沒有數據,因此會進入State_Failed狀態。
State_Failed狀態會從序列發生器中獲取數據,如果是通過yield方法獲取的數據,則會進入State_Ready狀態,如果是通過yieldAll方法獲取的數據,則會進入State_ManyReady狀態。
當從序列發生器中獲取數據時,如果是在State_ManyReady和State_Ready狀態,則直接發射一個數據,對應的進入到State_ManyNotReady和State_NotReady狀態。如果是在State_ManyNotReady和State_NotReady狀態,則會判斷是否有數據,如果有數據則對應進入到State_ManyReady和State_Ready狀態。如果沒有則進入到State_Failed狀態,獲取數據。
當序列發生器發射完畢時,會進入State_Done狀態。
接下來對SequenceBuilderIterator類進行分析。
SequenceBuilderIterator類繼承自SequenceScope類,實現了Iterator接口和Continuation接口。代碼如下:
private class SequenceBuilderIterator<T> : SequenceScope<T>(), Iterator<T>, Continuation<Unit> { // 迭代器的狀態 private var state = State_NotReady // 迭代器下一個要發送的值 private var nextValue: T? = null // 用于保存yieldAll方法傳入的迭代器 private var nextIterator: Iterator<T>? = null // 用于獲取下一個數據的續體 var nextStep: Continuation<Unit>? = null ... // 空的上下文 override val context: CoroutineContext get() = EmptyCoroutineContext }
為什么SequenceBuilderIterator類的上下文是空的呢?
因為SequenceBuilderIterator類繼承了SequenceScope類,因此該類也是受限的,因此不允許在類的擴展方法中調用類內以外的掛起方法。自然也就不能進行調度、攔截等操作,所以上下文為空。在協程中,受限協程的上下文一般都是空上下文。
yield方法與yieldAll方法是SequenceScope類中定義的兩個方法,在SequenceBuilderIterator類中的實現如下:
// 發射一個數據 override suspend fun yield(value: T) { // 保存數據到全局變量中 nextValue = value // 修改狀態 state = State_Ready // 掛起協程,獲取續體 return suspendCoroutineUninterceptedOrReturn { c -> // 保存續體到全局變量中 nextStep = c // 掛起 COROUTINE_SUSPENDED } } // 發射多個數據 override suspend fun yieldAll(iterator: Iterator<T>) { // 如果迭代器沒有數據,則直接返回 if (!iterator.hasNext()) return // 如果有數據,則保存到全局變量 nextIterator = iterator // 修改狀態 state = State_ManyReady // 掛起協程,獲取續體 return suspendCoroutineUninterceptedOrReturn { c -> // 保存續體到全局變量中 nextStep = c // 掛起 COROUTINE_SUSPENDED } }
通過上面的代碼可以知道,yield方法和yieldAll方法主要做了三件事情,掛起協程、修改狀態、保存要發送的數據和續體。而yieldAll發射多個數據原理在于保存了參數中Iterator接口指向的對象,通過迭代器獲取數據。
hasNext方法是Iterator接口中定義的方法,用于迭代時判斷是否還有數據,代碼如下:
override fun hasNext(): Boolean { // 循環 while (true) { // 判斷狀態 when (state) { // 剛通過yield方法發射數據 State_NotReady -> {} // 剛通過yieldAll方法發射數據 State_ManyNotReady -> // 如果迭代器中還有數據 if (nextIterator!!.hasNext()) { // 修改狀態,返回true state = State_ManyReady return true } else { // 沒有數據,則置空,丟棄迭代器 nextIterator = null } // 如果序列發生器已經發射完數據,返回false State_Done -> return false // 如果有數據,則直接返回true State_Ready, State_ManyReady -> return true // 其他狀態,則拋出異常 else -> throw exceptionalState() } // 走到這里,說明需要去獲取下一個數據 // 修改狀態 state = State_Failed // 獲取全局保存的續體 val step = nextStep!! // 置空 nextStep = null // 恢復序列發生器的執行,直到遇到yield方法或yieldAll方法掛起 step.resume(Unit) } } // 異常狀態的處理 private fun exceptionalState(): Throwable = when (state) { State_Done -> NoSuchElementException() State_Failed -> IllegalStateException("Iterator has failed.") else -> IllegalStateException("Unexpected state of the iterator: $state") }
next方法也是Iterator接口中定義的方法,用于在迭代器中存在數據時獲取數據,代碼如下:
override fun next(): T { // 判斷狀態 when (state) { // 如果當前處于已經發射完數據的狀態,則判斷是否有數據 State_NotReady, State_ManyNotReady -> return nextNotReady() // 如果通過yieldAll方法獲取到了數據 State_ManyReady -> { // 修改狀態 state = State_ManyNotReady // 通過迭代器獲取數據 return nextIterator!!.next() } // 如果通過yield方法獲取到了數據 State_Ready -> { // 修改狀態 state = State_NotReady // 獲取保存的數據并進行類型轉換 @Suppress("UNCHECKED_CAST") val result = nextValue as T // 全局變量置空 nextValue = null // 返回數據 return result } // 其他情況,則拋出異常 else -> throw exceptionalState() } } // 如果沒有數據,則拋出異常,有數據,則返回數據 private fun nextNotReady(): T { if (!hasNext()) throw NoSuchElementException() else return next() }
到此,相信大家對“Android協程作用域與序列發生器限制是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。