Kotlin 协程

下面这个博客对协程的讲解非常清楚

https://kaixue.io/kotlin-coroutines-1/

kotlin 官方中文资料

https://www.kotlincn.net/docs/reference/

协程的概念

        协程是一套线程框架,是对线程中执行的代码顺序的管理,协程中的代码依然在线程中运行;协程设计的初衷是为了解决并发问题,让协作式多任务实现起来更加方便。协程是一种编程思想,在其他语言中也有实现如Go、Python、Java的Kilim等。

举个协程的栗子

launch{
    val img = getImage(); // 耗时操作 需要等待
    show(img); // UI 显示图像 应在主线程执行
}

{}中的代码就是一段协程代码,其中getImage() 方法在子线程中执行,而show方法在主线程中执行,以同步代码的方式实现了异步逻辑,这段代码中并未使用回调函数将获取到的图像传递给show方法,如果都使用这样的写法可以避免出现回调地狱场景。

协程的创建

 

协程创建方式 说明 使用范围
runBlocking{...}

创建新的协程,运行在当前线程上,所以会堵塞当前线程,

直到协程体结束;但是这个runBlocking域中可以有多个协程,

多个协程可以并发进行,不会等待子协程执行结束

用于启动一个协程任务,通常只用于启动最外层的协程,

例如线程环境切换到协程环境

GlobalSocpe.launch{...}

启动一个新的线程,在新线程上创建运行协程,

不堵塞当前线程

需要启动异步线程处理的情况
CoroutineScope(Dispathcer.xxx).{...} 在指定类型的线程中创建协程,不会阻塞所运行的线程  

 

 

 

 

 

 

 

上面的launch方法可以的并列方法是async方法

  • lauch:协程构建器,创建并启动(也可以延时启动)一个协程,返回一个Job,用于监督和取消任务,用于无返回值的场景。
  • async:协程构建器,和launch一样,区别是返回一个Job的子类 Deferred,async可以在协程体中自定义返回值,并且通过Deferred.await堵塞当前线程等待接收async协程返回的类型。特别是需要启动异步线程处理并等待处理结果返回的场景

CoroutineScope 创建新一个子域,并管理域中的所有协程。注意这个方法只有在block中创建的所有子协程全部执行完毕后,才会退出。

SuperVisorScope 在子协程失败时,错误不会往上传递给父域,所以不会影响子协程。

线程切换

最常用的方法withContext(Dispatcher.xxx){code}

该方法让code代码运行在Dispatcher.xxx指定的线程中,  运行结束后再自动切回到调用withContext的线程。

suspend fun doSomething(){

    withContext(Dispathcer.IO){

        var img = getImage() // 耗时操作, getImag 是挂起函数

    }

}

如在主函数中调用doSomething() 则线程先从主线程切到IO线程,等协程执行完之后再切换回主线程

协程调度器

上述切换协程的运行线程使用了协程的调度器,协程的调度器有如下几种

调度器
类型 作用 场景
Dispatcher.Main 使协程运行在主线程 更新UI
Dispatcher.IO 使协程运行在IO线程 用于网络请求和文件访问
Dispatcher.Default 使用共享线程运行协程 CPU密集型任务
Dispatcher.UnConfined 使用父协程运行的线程 高级调度器,不应该在常规代码里使用
newSingleThreadContext 在新线程中运行协程  

 

 

 

 

 

 

 

 

挂起

抛物线的这篇文章对挂起的解释很清楚  https://kaixue.io/kotlin-coroutines-2/

挂起函数 关键字 suspend

suspend 关键字用来标识一个函数是挂起函数,但是suspend关键字标识的函数内部不一定有协程的挂起操作,如果函数使用了suspend关键字则函数只能在协程内或另一个挂起函数中被调用。

什么是挂起

aunch ,async 或者其他函数创建的协程,在执行到某一个 suspend 函数的时候,这个协程会被「suspend」,也就是被挂起。

那此时又是从哪里挂起?从当前线程挂起。换句话说,就是这个协程从正在执行它的线程上脱离。

注意,不是这个协程停下来了!是脱离,当前线程不再管这个协程要去做什么了。

suspend 是有暂停的意思,但我们在协程中应该理解为:当线程执行到协程的 suspend 函数的时候,暂时不继续执行协程代码了。

协程在执行到有 suspend 标记的函数的时候,会被 suspend 也就是被挂起,而所谓的被挂起,就是切个线程;不过区别在于,挂起函数在执行完成之后,协程会重新切回它原先的线程

再简单来讲,在 Kotlin 中所谓的挂起,就是一个稍后会被自动切回来的线程调度操作

当执行到一个挂起函数时,当前线程将不再执行这个挂起函数和该协程后续代码,直到这个挂起函数执行结束(或者这个挂起函数切回这个线程)。这个挂起函数仍然被执行,但可能在另一个线程中被继续执行,当然也可能继续在当前线程执行,根据代码中设置的调度器而定。

协程同步

存在多个协程时,有些协程直接没有交互关系,而有些协程需要另一个协程执行完的结果,对于没有关系的协程可以让他们并行执行,对于有关系的协程可以让他们异步执行。

runBlocking {
    Log.d("dd","zero")
    Log.d("dd",Thread.currentThread().name)
    GlobalScope.async(Dispatchers.Main) {
       Log.d("dd", "one")
       delay(200)
       Log.d("dd", "two")
    }  // .await()
    GlobalScope.async(Dispatchers.Main){
         Log.d("dd", "three")
         delay(100)
         Log.d("dd", "four")
    }  //.await()
    Log.d("dd","five")
 }
 Log.d("dd", "six")

 

Kotlin 协程_第1张图片   one->three->four->two 协程是并发进行的

增加await()方法后的输出:

Kotlin 协程_第2张图片  one->two->three->four 协程是按顺序执行的,第一个执行完再执行第二个

取消

取消一个协程可以使用cancel()方法Job#cancel()、 Deferred#cancel()、cancelAndrJoin()方法

kotlin提供的挂起函数都是可以取消的,自定义的挂起函数如果想可以被取消可以在挂起函数中判断isActive状态,当调用cancel()方法后这个状态会发生变化。

val job = launch {
    repeat(1000) { i ->
        println("job: I'm sleeping $i ...")
        delay(500L)
    }
}
delay(1300L) // 延迟一段时间
println("main: I'm tired of waiting!")
job.cancel() // 取消该作业
job.join() // 等待作业执行结束
println("main: Now I can quit.")

输出结果如下:

job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.

由于delay函数是coroutine的自带挂起函数,可取消所以运行到2就结束了。

val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
    var nextPrintTime = startTime
    var i = 0
    while (isActive) { // 可以被取消的计算循环
        // 每秒打印消息两次
        if (System.currentTimeMillis() >= nextPrintTime) {
            println("job: I'm sleeping ${i++} ...")
            nextPrintTime += 500L
        }
    }
}
delay(1300L) // 等待一段时间
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消该作业并等待它结束
println("main: Now I can quit.")

运行结果同上,这个协程中执行了耗时操作,可以将其抽象成一个挂起函数,注意while中的判断条件isActive,用来结束协程。如果监听这个状态而调用cancel()协程不会被取消知道运行结束。

当一个父协程被取消的时候,所有它的子协程也会被递归的取消。然而,当使用 GlobalScope 来启动一个协程时,则新协程的作业没有父作业。 因此它与这个启动的作用域无关且独立运作。

超时

携程的执行时间可以受到约束,我们使用withTimeout、withTimeoutOrNull

withTimeout(time)方法如果超时了会爆出一个TimeoutCancelleationException,而withTimeoutOrNull则返回null

状态

协程的生命周期

 

你可能感兴趣的:(Kotlin)