Kotlin协程

202 阅读3分钟

Kotlin协程使用

Android中使用协程依赖添加,Google Developer

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'

implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"

PS:AndroidStudio中查看依赖树方式,执行./gradlew :app:dependencie

./gradlew :app:dependencie

\--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1
     +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1
     |    \--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.1
     |         +--- org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.1
     |         |    +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1 (c)
     |         |    +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1 (c)
     |         |    \--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.1 (c)
     |         +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.0 -> 1.7.20 (*)
     |         \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.6.0 -> 1.7.20
     +--- org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.1 (*)
     \--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.0 -> 1.7.20 (*)

在新建项目时只需要添加依赖appcompat,基本上就包含Jetpack 相关的部分组件(LiveData\ViewModel)

implementation 'androidx.appcompat:appcompat:1.5.1'

协程启动方式

协程最常用的启动方式launch、async方式,另外还有通过produce、actor创建channel来实现协程启动

class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
    companion object{
        private const val TAG = "MainActivity"
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        WindowCompat.setDecorFitsSystemWindows(window, false)
        super.onCreate(savedInstanceState)
        val deferred = async {
            delay(1000)
            1
        }
        launch {
            Log.d(TAG, "${deferred.await()}")
        }
        
        var count = 0
        val receiverChannel = produce(Dispatchers.Unconfined) {
            while (count<=10){
                Log.d(TAG, "produce send $count")
                send(count++)
                delay(1000)
            }
        }
        launch{
            val iterator = receiverChannel.iterator()
            while (iterator.hasNext()){
                Log.d(TAG, "produce receive ${iterator.next()}")
            }
        }

        val sendChannel = actor<Int> {
            while (iterator().hasNext()){
                Log.d(TAG, "actor receive ${receive()}")
            }
        }
        launch {
            while (count<=10){
                Log.d(TAG, "actor send $count")
                sendChannel.send(count++)
                delay(1000)
            }
        }
    }
}

协程启动参数中包含3个参数,协程上下文、启动模式、协程体,协程通过协程作用域进行启动(CoroutineScope),在目前CPU执行速度上比较,DEFAULT\ATOMIC 结果基本一致,LAZY就需要主动调用后生效,UNDISPATCHED模式会在当前线程立即执行,直到遇到第一个挂起函数后执行就取决于挂起点本身的逻辑以及上下文当中的调度器

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}
public enum class CoroutineStart {
    DEFAULT,//立即执行协程体,如果协程执行之前被取消,就不会执行
    LAZY,//只有在需要的情况下运行,需调用join\start启动协程
    ATOMIC,//立即执行协程体,但在开始运行之前无法取消,运行在launch设置的协程中
    UNDISPATCHED,//立即在当前线程执行协程体,直到第一个 suspend 调用,
}
launch(Dispatchers.IO, start = CoroutineStart.DEFAULT) {
    log("DEFAULT")
}
val job = launch(Dispatchers.IO, start = CoroutineStart.LAZY) {
    log("LAZY")
}
launch(Dispatchers.IO, start = CoroutineStart.ATOMIC) {
    log("ATOMIC")
}
launch(Dispatchers.IO, start = CoroutineStart.UNDISPATCHED) {
    log("UNDISPATCHED")
}
binding.start.setOnClickListener {
    log("点击执行")
    job.start()
}

System.out: test1  DEFAULT  DefaultDispatcher-worker-1  23:54:20:061
System.out: test1  ATOMIC  DefaultDispatcher-worker-1  23:54:20:061
System.out: test1  UNDISPATCHED  main  23:54:20:061
System.out: test1  点击执行  main  23:54:20:061
System.out: test1  LAZY  DefaultDispatcher-worker-3  23:54:20:061
System.out: test1  点击执行  main  23:54:20:061
System.out: test1  点击执行  main  23:54:20:061

协程上下文CoroutineContext

简单理解CorontineContext为一个链表结构,通过EmptyCoroutineContext、CombinedContext进行组合,将Element作为元素进行封装,比较常用的几种Element

  • Job
  • CoroutineName
  • CoroutineDispatcher
  • ContinuationInterceptor
  • CoroutineExceptionHandler
public interface CoroutineContext {
    public operator fun <E : Element> get(key: Key<E>): E?
    public fun <R> fold(initial: R, operation: (R, Element) -> R): R
    public operator fun plus(context: CoroutineContext): CoroutineContext =
        if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
            context.fold(this) { acc, element ->
                val removed = acc.minusKey(element.key)
                if (removed === EmptyCoroutineContext) element else {
                    // make sure interceptor is always last in the context (and thus is fast to get when present)
                    val interceptor = removed[ContinuationInterceptor]
                    if (interceptor == null) CombinedContext(removed, element) else {
                        val left = removed.minusKey(ContinuationInterceptor)
                        if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                            CombinedContext(CombinedContext(left, element), interceptor)
                    }
                }
            }

    public fun minusKey(key: Key<*>): CoroutineContext

    public interface Key<E : Element>

    public interface Element : CoroutineContext {
        public val key: Key<*>

        public override operator fun <E : Element> get(key: Key<E>): E? =
            @Suppress("UNCHECKED_CAST")
            if (this.key == key) this as E else null

        public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
            operation(initial, this)

        public override fun minusKey(key: Key<*>): CoroutineContext =
            if (this.key == key) EmptyCoroutineContext else this
    }
}

协程调度器CoroutineDispatcher

  • Default 线程池
  • Main UI线程,Android主线程
  • IO 线程池,基于 Default 调度器背后的线程池,并实现了独立的队列和限制,因此协程调度器从 Default 切换到 IO 并不会触发线程切换
  • Unconfined直接执行

协程拦截器ContinuationInterceptor

private class MyContinuation<T>(val continuation: Continuation<T>) : Continuation<T> {
    override val context: CoroutineContext
        get() = continuation.context

    override fun resumeWith(result: Result<T>) {
        log("<MyContinuation> $result ${context[CoroutineName]} ")
        continuation.resumeWith(result)
    }
}

private val myContinuationInterceptor = object : ContinuationInterceptor {
    override val key: CoroutineContext.Key<*>
        get() = ContinuationInterceptor

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return MyContinuation(continuation)
    }
}

协程异常处理器CoroutineExceptionHandler

private val coroutineExceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        log("$throwable ${coroutineContext[CoroutineName]}")
    }
//代码使用示例
launch(
    Dispatchers.IO + myContinuationInterceptor + coroutineExceptionHandler+CoroutineName("①"),
    start = CoroutineStart.DEFAULT
) {
    log("DEFAULT")
    delay(1000)
    log("DEFAULT coroutineScope")
    throw NullPointerException("DEFAULT_嘿嘿")
}
日志打印如下:
test1  <MyContinuation> Success(kotlin.Unit) CoroutineName(①)    05:31:35:718   main
test1  DEFAULT   05:31:35:718   main
test1  UNDISPATCHED   05:31:35:718   main
test1  ATOMIC   05:31:35:718   DefaultDispatcher-worker-3
test1  <MyContinuation> Success(kotlin.Unit) CoroutineName(①)    05:31:35:718   kotlinx.coroutines.DefaultExecutor
test1  DEFAULT coroutineScope   05:31:35:718   kotlinx.coroutines.DefaultExecutor
test1  java.lang.NullPointerException: DEFAULT_嘿嘿 CoroutineName(①)   05:31:35:718   kotlinx.coroutines.DefaultExecutor
协程异常传播

关于协程异常传播就涉及到一个概念,协同作用域coroutineScope、主从作用域supervisorScope,结论总结如下:

  1. coroutineScope中子协程产生的异常会触发父协程取消,同时会影响兄弟协程取消;
  2. supervisorScope中子协程产生的异常不会向上传递,会在父协程中的异常处理器中捕获;
  3. 两种作用域如果在父协程中不进行异常捕获均会造成程序崩溃;
//顶层作用域(父协程)需捕获异常,否则会引起程序崩溃
launch(coroutineExceptionHandler+CoroutineName("父协程")) {
    supervisorScope {
        launch(CoroutineName("①")) {
            delay(1000)
            log("子协程不受影响")
        }
        launch(CoroutineName("②")) {
            delay(500)
            log("子协程抛出异常")
            throw NullPointerException("coroutineScope 抛出异常")
        }
    }
}

System.out: test1  子协程抛出异常   02:19:42:040   main
System.out: test1  CoroutineExceptionHandler  java.lang.NullPointerException: coroutineScope 抛出异常 CoroutineName(②)   02:19:42:040   main
System.out: test1  子协程受影响   02:19:42:040   main
launch(coroutineExceptionHandler+CoroutineName("父协程")) {
    coroutineScope {
        launch(CoroutineName("①")) {
            delay(1000)
            log("子协程受影响")
        }
        launch(CoroutineName("②")) {
            delay(500)
            log("子协程抛出异常")
            throw NullPointerException("coroutineScope 抛出异常")
        }
    }
}
System.out: test1  子协程抛出异常   02:19:42:040   main
System.out: test1  CoroutineExceptionHandler  java.lang.NullPointerException: coroutineScope 抛出异常 CoroutineName(父协程)   02:19:42:040   main

通过async启动协程时,只有在await时才会触发异常,异常传递不会向上传播,而是在调用await()方法所处的协程作用域中传播

val deferred = async(coroutineExceptionHandler+CoroutineName("async父协程")) {
    delay(500)
    throw NullPointerException("coroutineScope 抛出异常")
    1
}
launch(coroutineExceptionHandler+CoroutineName("launch父协程")) {
    deferred.await()
}

System.out: test1  CoroutineExceptionHandler  java.lang.NullPointerException: coroutineScope 抛出异常 CoroutineName(launch父协程)   02:45:44:714   main

协程取消

调用job/deferred 的cancel方法可以触发协程取消操作,通过suspendCancellableCoroutine可以让suspend()方法支持协程的取消操作,通过监听invokeOnCancellation回调获取suspend()方法所处协程的状态

val job = launch(Dispatchers.Default) {
    log(1)
    val user = getUser()
    log(user)
    log(2)
}
log(3)
job.cancel()
log(4)

private suspend fun getUser():String = suspendCancellableCoroutine {
    it.invokeOnCancellation {
        log("invokeOnCancellation is invoked")
    }
    it.resume("wang")
}

System.out: test1  3   03:18:15:330   main
System.out: test1  1   03:18:15:330   DefaultDispatcher-worker-1
System.out: test1  4   03:18:15:330   main
System.out: test1  invokeOnCancellation is invoked   03:18:15:330   DefaultDispatcher-worker-1