广告位联系
返回顶部
分享到

Android协程作用域与序列发生器限制介绍梳理

Android 来源:互联网 作者:佚名 发布时间:2022-08-26 10:48:07 人浏览
摘要

一.受限协程作用域 在协程的基础与使用中提到,可以通过sequence方法构建一个序列发生器。但当在sequence方法中调用除了yield方法与yieldAll方法以外的其他挂起方法时,就会报错。比如在

一.受限协程作用域

在协程的基础与使用中提到,可以通过sequence方法构建一个序列发生器。但当在sequence方法中调用除了yield方法与yieldAll方法以外的其他挂起方法时,就会报错。比如在sequence方法中调用delay方法,就会产生下面的报错提示:

翻译过来大致是“受限的挂起方法只能调用自身受限的协程作用域内的成员变量或挂起方法。这是什么意思呢?

1.sequence方法

sequence方法就是构建序列发生器用到的方法,内部通过Sequence方法实现,代码如下:

1

2

@SinceKotlin("1.3")

public fun <T> sequence(@BuilderInference block: suspend SequenceScope<T>.() -> Unit): Sequence<T> = Sequence { iterator(block) }

其中参数block是一个在SequenceScope环境下的lambda表达式。

2.SequenceScope类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// 注意

@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注解修饰,代码如下:

1

2

3

4

@SinceKotlin("1.3")

@Target(AnnotationTarget.CLASS)

@Retention(AnnotationRetention.BINARY)

public annotation class RestrictsSuspension

RestrictsSuspension注解用于修饰一个类或接口,表示该类是受限的。在被该注解修饰的类的扩展挂起方法中,只能调用该注解修饰的类中定义的挂起方法,不能调用其他类的挂起方法。

具体的,在sequence方法中,block就是SequenceScope类的扩展方法,因此在block中,只能使用SequenceScope类中提供的挂起方法——yield方法和yieldAll方法。同时,SequenceScope类的构造器被internal修饰,无法在外部被继承,因此也就无法定义其他的挂起方法。

为什么受限协程作用域不允许调用其他的挂起方法呢?

因为当一个方法挂起协程时,会获取协程的续体,同时协程需要等待方法执行完毕后的回调,这意味着会暴露协程的续体。可能会造成挂起协程执行的不确定性。

二.序列发生器

1.Sequence接口

首先来分析一下Sequence接口,代码如下:

1

2

3

public interface Sequence<out T> {

    public operator fun iterator(): Iterator<T>

}

2.Sequence方法

在协程中,有一个与Sequence接口同名的方法,该方法用于返回一个实现了Sequence接口的对象,代码如下:

1

2

3

4

@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方法的参数。

3.iterator方法

1

2

3

4

5

6

@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类中。

4.SequenceBuilderIterator类

SequenceBuilderIterator类是用于对序列发生器进行迭代,在该类的内部对状态进行了划分,代码如下:

1

2

3

4

5

6

7

8

9

10

11

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类进行分析。

1.SequenceBuilderIterator类的全局变量

SequenceBuilderIterator类继承自SequenceScope类,实现了Iterator接口和Continuation接口。代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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类,因此该类也是受限的,因此不允许在类的扩展方法中调用类内以外的挂起方法。自然也就不能进行调度、拦截等操作,所以上下文为空。在协程中,受限协程的上下文一般都是空上下文。

2.yield方法与yieldAll方法

yield方法与yieldAll方法是SequenceScope类中定义的两个方法,在SequenceBuilderIterator类中的实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

// 发射一个数据

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接口指向的对象,通过迭代器获取数据。

3.hasNext方法

hasNext方法是Iterator接口中定义的方法,用于迭代时判断是否还有数据,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

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")

}

4.next方法

next方法也是Iterator接口中定义的方法,用于在迭代器中存在数据时获取数据,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

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()

}

5.总结

当使用序列发生器进行迭代时,首先会调用hasNext方法,hasNext方法会通过保存的续体,恢复序列发生器所在的协程继续执行,获取下一次待发射的数据。如果获取了到数据,则会返回true,这样之后通过next方法就可以获取到对应的数据。

当序列发生器所在的协程在执行中遇到yield方法时,会发生挂起,同时将下一次待发射的数据保存起来。如果遇到的是yieldAll方法,则保存的是迭代器,下一次发射数据时会从迭代器中获取


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://blog.csdn.net/LeeDuoZuiShuai/article/details/126323313
相关文章
  • Kotlin的Collection与Sequence操作异同点介绍

    Kotlin的Collection与Sequence操作异同点介绍
    在Android开发中,集合是我们必备的容器,Kotlin的标准库中提供了很多处理集合的方法,而且还提供了两种基于容器的工作方式:Collection 和
  • 实现一个Kotlin函数类型方法

    实现一个Kotlin函数类型方法
    接口与函数类型 业务开发中,经常会有实现一个函数式接口(即接口只有一个方法需要实现)的场景,大家应该都会不假思索的写出如下代
  • Android10 App启动Activity源码分析
    ActivityThread的main方法 让我们把目光聚焦到ActivityThread的main方法上。 ActivityThread的源码路径为/frameworks/base/core/java/android/app/ActivityThread。 1 2
  • Android10客户端事务管理ClientLifecycleManager源码解析

    Android10客户端事务管理ClientLifecycleManager源码解析
    在Android 10 App启动分析之Activity启动篇(二)一文中,简单地介绍了Activity的生命周期管理器是如何调度Activity进入onCreate生命周期的流程。这
  • Kotlin对象的懒加载方式by lazy与lateinit异同介绍

    Kotlin对象的懒加载方式by lazy与lateinit异同介绍
    属性或对象的延时加载是我们相当常用的,一般我们都是使用 lateinit 和 by lazy 来实现。 他们两者都是延时初始化,那么在使用时那么他们两
  • Android类加载流程分析

    Android类加载流程分析
    本文分析的代码基于Android8.1.0源码。 流程分析 从loadClass开始,我们来看下Android中类加载的流程 /libcore/ojluni/src/main/java/java/lang/ClassLoader.ja
  • Android实现读写USB串口数据的代码

    Android实现读写USB串口数据的代码
    最近在研究USB方面的内容;先后做了关于Android读写HID、串口设备的DEMO。本文比较简单,主要介绍的是Android实现读取串口数据的功能 废话不
  • Epoxy - 在RecyclerView中构建复杂界面
    Diffing 对于复杂数据结构支持的多个视图类型展示在屏幕上, Epoxy此时是尤其有用的. 在这些场景中, 数据可能会被网络请求, 异步 Observable, 用
  • Android性能优化的详细介绍

    Android性能优化的详细介绍
    性能优化是一个app很重要的一部分,一个性能优良的app从被下载到启动到使用都能给用户到来很好的体验。自然我们做性能优化也是从被下
  • Android进阶宝典-插件化2(Hook启动插件中四大组件

    Android进阶宝典-插件化2(Hook启动插件中四大组件
    在上一节,我们主要介绍了如果通过反射来加载插件中的类,调用类中的方法;既然插件是一个apk,其实最重要的是启动插件中的Activity、
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计