美文网首页
八,Kotlin协程详解

八,Kotlin协程详解

作者: CarlosLynn | 来源:发表于2021-02-22 21:31 被阅读0次

概念

Kotlin协程是一个异步框架,是建立在线程基础上轻量级的线程.
协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。
总而言之:
协程可以简化异步编程,可以顺序地表达程序,协程也提供了一种避免阻塞线程并用更廉价、更可控的操作替代线程阻塞的方法 -- 协程挂起

挂起:保存当前运行状态,释放资源,此时协程可去做其它工作,可充分利用资源
阻塞:占用资源未释放,等待状态
使用之前首选需要引入依赖

//kotlin 标准库
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
//依赖协程核心库 ,提供Android UI调度器
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8'
//依赖当前平台所对应的平台库 (必须)
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1"

协程的优势

  • 轻量级,占用更少的系统资源

  • 更高的执行效率

  • 挂起函数较于实现Runnable或Callable接口更加方便可控

  • kotlin.coroutine 核心库的支持,让编写异步代码更加简单

启动协程

launch:job 简单使用示意
GlobalScope 单例对象 可以直接调用 launch 开启协程
创建一个不会阻塞当前线程、没有返回结果的 Coroutine,但会返回一个 Job 对象,可以用于控制这个 Coroutine 的执行和取消,返回值为Job。

// 默认在后台执行,可指定线程
GlobalScope.launch {
    LogUtil.i("zz------${Thread.currentThread().name}")
}
//LogUtil: i: zz------DefaultDispatcher-worker-1

runBlocking:T顶层函数

创建一个会阻塞当前线程的Coroutine,常用于单元测试的场景,开发中一般不会用到
runBlocking {}是创建一个新的协程同时阻塞当前线程,直到协程结束。这个不应该在协程中使用,主要是为main函数和测试设计的

fun main(args: Array<String>) = runBlocking<Unit> { // start main coroutine
    launch { // launch new coroutine in background and continue
        delay(1000L)
        println("World!")
    }
    println("Hello,") // main coroutine continues here immediately
    delay(2000L)      // delaying for 2 seconds to keep JVM alive
}

async/await:Deferred
async 返回的 Coroutine 多实现了 Deferred 接口,简单理解为带返回值的launch函数
async {}可以实现与 launch builder 一样的效果,在后台创建一个新协程,唯一的区别是它有返回值,因为async {}返回的是 Deferred 类型。
获取async {}的返回值需要通过await()函数,它也是是个挂起函数,调用时会挂起当前协程直到 async 中代码执行完并返回某个值。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_demo2)
    testAsync()
}

private fun testAsync() = GlobalScope.launch {
    val deferred = async(Dispatchers.IO) {
        delay(3000L)
        "Show Time"
    }
    // 此处获取耗时任务的结果,我们挂起当前协程,并等待结果
    val result = deferred.await()
    //挂起协程切换至UI线程 展示结果
    withContext(Dispatchers.Main) {
        tvText.text = result
    }
}

async和await是两个函数,这两个函数在我们使用过程中一般都是成对出现的。
async用于启动一个异步的协程任务,await用于去得到协程任务结束时返回的结果,结果是通过一个Deferred对象返回的。

挂起函数

suspend 修饰的方法称之为挂起函数,有标记和提醒作用
挂起函数只能在协程或者另外一个挂起函数中使用,在协程中挂起函数结束后自动切回协程主线程
挂起,就是一个稍后会被自动切回来的线程调度操作。

// 设置主线程线程环境
GlobalScope.launch(Dispatchers.Main) {
    // suspend,挂起函数,方法内切线程
    ioFun1()
    // 自动回到协程主线程,上面指定主线程
    uiFun1()
}

/*
suspend 修饰的方法称之为挂起函数,有标记和提醒作用
   挂起函数只能在协程或者另外一个挂起函数中使用,在协程中挂起函数结束后自动切回“协程主线程”
*/
private suspend fun ioFun1() {
    // 切换线程
    withContext(Dispatchers.IO) {
        LogUtil.i("zz------ioFun1--${Thread.currentThread().name}")
    }
}

private fun uiFun1() {
    LogUtil.i("zz------uiFun1--${Thread.currentThread().name}")
}
I/LogUtil: i: zz------ioFun1--DefaultDispatcher-worker-1
I/LogUtil: i: zz------uiFun1--main

取消协程

未取消协程的执行

val launch = GlobalScope.launch {
    LogUtil.i("launch------${Thread.currentThread().name}")
    // 耗时任务
    delay(5000)
    LogUtil.i("delay------${Thread.currentThread().name}")
}
//执行结果:
I/LogUtil: i: launch------DefaultDispatcher-worker-1
I/LogUtil: i: delay------DefaultDispatcher-worker-1

取消协程后的执行

val launch = GlobalScope.launch {
    LogUtil.i("launch------${Thread.currentThread().name}")
    // 耗时任务
    delay(5000)
    LogUtil.i("delay------${Thread.currentThread().name}")
}
// 取消协程
launch.cancel()
//执行结果
I/LogUtil: i: launch------DefaultDispatcher-worker-1

从日志的输入情况来看,协程取消后其后续的代码不在执行.

线程的切换

// 开启协程
GlobalScope.launch {
    LogUtil.i("launch------${Thread.currentThread().name}")
    // 切换到主线程
    withContext(Dispatchers.Main) {
        LogUtil.i("launch------${Thread.currentThread().name}")
    }
}

I/LogUtil: i: launch------DefaultDispatcher-worker-1
I/LogUtil: i: launch------main

从日志的输出结果可以看到线程从子线程切换到了主线程中.
使用GlobalScope.launch函数开启协程。在其中,使用withContext(Dispatchers.Main)将线程切换到主线程

CoroutineDispatcher ,调度器

协程调度器,决定协程所在的线程或线程池。它可以指定协程运行于特定的一个线程、一个线程池或者不指定任何线程(这样协程就会运行于当前线程)。coroutines-core中 CoroutineDispatcher 有两种标准实现 CommonPool 和 Unconfined,Unconfined 就是不指定线程。

launch函数定义中的DefaultDispatcher实际上就是CommonPool,CommonPool是一个协程调度器,其指定的线程为共有的线程池。而且 CoroutineDispatcher 实现了 CoroutineContext 接口,所以才能直接指定context: CoroutineContext = DefaultDispatcher,实际上,协程上下文中的元素都实现了 CoroutineContext 接口
Kotlin内置了四种调度器:

  • Dispatchers.Default
    默认的调度器,基于JVM上的共享线程池,最大线程数为CPU核心数。
  • Dispatchers.IO
    专为IO操作设计的调度器,默认最大线程数为64与CPU核心数的较大值。
  • Dispatchers.Main
    Android中的主线程,UI主线程调度器。
  • Dispatchers.Unconfined
    无限制调度器。在第一个挂起点之前,在调用它的线程中执行;之后由该挂起函数决定。

GlobalScope

GlobalScope是一个特殊的全局CoroutineScope,它不与任何Job绑定。GlobalScope只应该使用在生命周期与整个应用程序相同、且不被取消的协程中。

CoroutineContext

CoroutineContext,协程上下文,是一些元素的集合,主要包括 Job 和 CoroutineDispatcher 元素,可以代表一个协程的场景。
EmptyCoroutineContext 表示一个空的协程上下文。

Job & Deferred

Job,任务,封装了协程中需要执行的代码逻辑。Job 可以取消并且有简单生命周期,它有三种状态

    //当 Job 处于活动状态时为 true
    //如果 Job 未被取消或没有失败,则均处于 active 状态
    public val isActive: Boolean

    //当 Job 正常结束或者由于异常结束,均返回 true
    public val isCompleted: Boolean

    //当 Job 被主动取消或者由于异常结束,均返回 true
    public val isCancelled: Boolean

    //启动 Job
    //如果此调用的确启动了 Job,则返回 true
    //如果 Job 调用前就已处于 started 或者是 completed 状态,则返回 false 
    public fun start(): Boolean

    //用于取消 Job,可同时通过传入 Exception 来标明取消原因
    public fun cancel(cause: CancellationException? = null)

    //阻塞等待直到此 Job 结束运行
    public suspend fun join()

    //当 Job 结束运行时(不管由于什么原因)回调此方法,可用于接收可能存在的运行异常
    public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
复制代码

Coroutine builders

launch函数属于协程构建器 Coroutine builders,Kotlin 中还有其他几种 Builders,负责创建协程

相关文章

网友评论

      本文标题:八,Kotlin协程详解

      本文链接:https://www.haomeiwen.com/subject/tiiotltx.html