Kotlin协程了解一下

协程是啥玩意?

官方描述html

协程经过将复杂性放入库来简化异步编程。程序的逻辑能够在协程中顺序地表达,而底层库会为咱们解决其异步性。该库能够将用户代码的相关部分包装为回调、订阅相关事件、在不一样线程(甚至不一样机器)上调度执行,而代码则保持如同顺序执行同样简单。android

人话版编程

协程就像很是轻量级的线程。promise

协程是运行在单线程中的并发程序,避免了多线程并发机制中切换线程时带来的线程上下文切换、线程状态切换、线程初始化上的性能损耗,能大幅度唐提升并发性能bash

线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,可是协程挂起时不须要阻塞线程,几乎是无代价的,协程是由开发者控制的。因此协程也像用户态的线程,很是轻量级,一个线程中能够建立任意个协程。多线程

协程是跑在线程上的,一个线程能够同时跑多个协程,每个协程则表明一个耗时任务,咱们手动控制多个协程之间的运行、切换,决定谁何时挂起,何时运行,何时唤醒,而不是线程那样交给系统内核来操做去竞争CPU时间片,缺点是本质是个单线程,不能利用到单个CPU的多个核闭包

怎么使用?

添加依赖kotlinx.coroutines并发

kotlinx.coroutines 是由 JetBrains 开发的功能丰富的协程库。它包含本指南中涵盖的不少启用高级协程的原语,包括 launch、 async 等等。异步

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
}
复制代码

固然你确定须要引入kotlin的async

buildscript {
    ext.kotlin_version = '1.3.61'
}

repository {
    jcenter()
}
复制代码

Hello协程

import kotlinx.coroutines.*

fun main() {
    GlobalScope.launch { // 在后台启动一个新的协程并继续
        delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
        println("World!") // 在延迟后打印输出
    }
    println("Hello,") // 协程已在等待时主线程还在继续
    Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}
复制代码

delay 是一个特殊的 挂起函数 ,它不会形成线程阻塞,可是会 挂起 协程,而且只能在协程中使用。

你但愿阻塞线程仍是不阻塞?

调用了runBlocking 的线程会一直阻塞直到 runBlocking 内部的协程执行完毕。

import kotlinx.coroutines.*

fun main() = runBlocking<Unit> { // 开始执行主协程
    GlobalScope.launch { // 在后台启动一个新的协程并继续
        delay(1000L)
        println("World!")
    }
    println("Hello,") // 主协程在这里会当即执行
    delay(2000L)      // 延迟 2 秒来保证 JVM 存活
}
复制代码

这里的 runBlocking { …… } 做为用来启动顶层主协程的适配器。 咱们显式指定了其返回类型 Unit,由于在 Kotlin 中 main 函数必须返回 Unit 类型。

launch建立协程

async 建立带返回值的协程,返回的是 Deferred 类

async 同 launch 区别就是 async 是有返回值的 async 返回的是 Deferred 类型,Deferred 继承自 Job 接口,Job有的它都有,增长了一个方法 await ,这个方法接收的是 async 闭包中返回的值,async 的特色是不会阻塞当前线程,但会阻塞所在协程,也就是挂起

withContext 不建立新的协程,在指定协程上运行代码块

runBlocking 不是 GlobalScope 的 API,能够独立使用,区别是 runBlocking 里面的 delay 会阻塞线程,而 launch 建立的不会

协程很轻量

import kotlinx.coroutines.*

fun main() = runBlocking {
    repeat(100_000) { // 启动大量的协程
        launch {
            delay(1000L)
            print(".")
        }
    }
}
复制代码

它启动了 10 万个协程,而且在一秒钟后,每一个协程都输出一个点。 如今,尝试使用线程来实现。会发生什么?(极可能你的代码会产生某种内存不足的错误)

launch 建立协程, launch是个扩展函数,接受3个参数,前面2个是常规参数,最后一个是个对象式函数

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
}
复制代码

launch 的3个参数和返回值

CoroutineContext

能够理解为协程的上下文,在这里咱们能够设置 CoroutineDispatcher 协程运行的线程调度器,有 4种线程模式:

Dispatchers.Default

Dispatchers.IO 子线程

Dispatchers.Main 主线程

Dispatchers.Unconfined 没指定,就是在当前线程

不写的话就是 Dispatchers.Default 模式的,或者咱们能够本身建立协程上下文,也就是线程池

//newSingleThreadContext 单线程,newFixedThreadPoolContext 线程池
val singleThreadContext = newSingleThreadContext("work")
GlobalScope.launch(singleThreadContext) {

}
复制代码

CoroutineStart

启动模式,默认是DEAFAULT,也就是建立就启动;还有一个是LAZY,意思是等你须要它的时候,再调用启动

val lazyStart = GlobalScope.launch(start = CoroutineStart.LAZY) {
                RLogUtil.i("启动了")
            }
            delay(1000)
            lazyStart.start()
复制代码

block

闭包方法体,定义协程内须要执行的操做

返回值

协程构建函数的返回值,能够把返回值job当作协程对象自己,协程的操做方法都在job身上了

job.start() - 启动协程,除了 lazy 模式,协程都不须要手动启动

job.join() - 等待协程执行完毕

job.cancel() - 取消一个协程

job.cancelAndJoin() - 等待协程执行完毕而后再取消

suspend 修饰符

当你对这段代码执行“提取函数”重构时,你会获得一个带有 suspend 修饰符的新函数。 那是你的第一个挂起函数

在协程中表现为标记、切换方法、代码段,协程里使用suspend关键字修饰方法,既该方法能够被协程挂起,没用suspend修饰的方法不能参与协程任务,suspend修饰的方法只能在协程中只能与另外一个suspend修饰的方法使用

// 这是你的第一个挂起函数
suspend fun doWorld() {
    delay(1000L)
    println("World!")
}
复制代码

如何取消

使用job.cancel()取消,也可使用job.cancelAndJoin() - 等待协程执行完毕而后再取消

import kotlinx.coroutines.*

fun main() = runBlocking {
    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.")
}
复制代码

取消是协做的这里须要注意的是一段协程代码必须协做才能被取消。 全部 kotlinx.coroutines 中的挂起函数都是 可被取消的 。它们检查协程的取消, 并在取消时抛出CancellationException。然而,若是协程正在执行计算任务,而且没有检查取消的话,那么它是不能被取消的

import kotlinx.coroutines.*

fun main() = runBlocking {
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (i < 5) { // 一个执行计算的循环,只是为了占用 CPU
            // 每秒打印消息两次
            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.")
}
复制代码
job: I'm sleeping 0 ... job: I'm sleeping 1 ...
job: I'm sleeping 2 ... main: I'm tired of waiting!
job: I'm sleeping 3 ... job: I'm sleeping 4 ...
main: Now I can quit.
复制代码

咱们有两种方法来使执行计算的代码能够被取消。第一种方法是按期调用挂起函数来检查取消。对于这种目的 yield 是一个好的选择。 另外一种方法是显式的检查取消状态

将前一个示例中的 while (i < 5) 替换为 while (isActive) 并从新运行它。结果显示是能够取消的

咱们一般使用以下的方法处理在被取消时抛出 CancellationException 的可被取消的挂起函数。好比说,try {……} finally {……} 表达式以及 Kotlin 的 use 函数通常在协程被取消的时候执行它们的终结动做

超时

在实践中绝大多数取消一个协程的理由是它有可能超时。 当你手动追踪一个相关 Job 的引用并启动了一个单独的协程在延迟后取消追踪,这里已经准备好使用 withTimeout 函数来作这件事。

withTimeout 抛出了 TimeoutCancellationException,它是 CancellationException 的子类 来看看示例代码:

import kotlinx.coroutines.*

fun main() = runBlocking {
    withTimeout(1300L) {
        repeat(1000) { i ->
                println("I'm sleeping $i ...")
            delay(500L)
        }
    }
}
复制代码

使用 async 并发

在概念上,async 就相似于 launch。它启动了一个单独的协程,这是一个轻量级的线程并与其它全部的协程一块儿并发的工做。不一样之处在于 launch 返回一个 Job 而且不附带任何结果值,而 async 返回一个 Deferred —— 一个轻量级的非阻塞 future, 这表明了一个将会在稍后提供结果的 promise。你可使用 .await() 在一个延期的值上获得它的最终结果, 可是 Deferred 也是一个 Job,因此若是须要的话,你能够取消它。

import kotlinx.coroutines.*
import kotlin.system.*

fun main() = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async { doSomethingUsefulOne() }
        val two = async { doSomethingUsefulTwo() }
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")    
}

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // 假设咱们在这里作了些有用的事
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // 假设咱们在这里也作了些有用的事
    return 29
}
复制代码

她们之间是会同时执行doSomethingUsefulOne()和doSomethingUsefulTwo()不使用的话则是按顺序执行

惰性启动的 async

可选的,async 能够经过将 start 参数设置为 CoroutineStart.LAZY 而变为惰性的。 在这个模式下,只有结果经过 await 获取的时候协程才会启动,或者在 Job 的 start 函数调用的时候。

写到最后

上述内容也只是带大家熟悉一下协程,学习协程可能会费些时间,协程和线程相似可是和线程有很大区别,特别须要注意一下协程内的使用顺序,协程是很轻量级的,很是适合作一些并发任务,协程仍是有一些高级操做的,如异步流点(看着有点像RxJava)熟练了协程以后基本就能够替代RxJava,协程三连走起!

你们看到这里也基本熟悉了kotlin协程的使用了,本文大多数内容源自于kotlin官网,关于协程仍是一些高级用法,我们下期再见!

感谢

相关文章
相关标签/搜索