首页 > 其他分享 >Kotlin协程系列(一)

Kotlin协程系列(一)

时间:2023-11-23 21:04:00浏览次数:23  
标签:调用 协程 函数 Kotlin 线程 Continuation fun 系列

一.协程的定义

  最近看了一本有关kotlin协程的书籍,对协程又有了不一样的了解,所以准备写一个关于kotlin协程系列的文章。

  言归正传,我们在学习一个新东西的时候,如果连这个东西"是什么"都回答不了,那么自然很难进入知识获取阶段的"为什么"和"怎么办"这两个后续环节了。因此,我们首先得知道协程的定义。

  协程的概念最核心的点就是一段程序能够被挂起,稍后再在挂起的位置恢复。并且,挂起和恢复是开发者的程序逻辑自己控制的,协程是通过主动挂起让出运行权来实现协作的,因此它本质上是在讨论程序控制流程的机制,这是最核心的点,任何场景下讨论协程都能落脚到挂起和恢复。

二.协程和线程的联系和区别

  联系:协程和线程都可以实现并发性,允许程序在同一时间处理多个任务;协程和线程都可以用于异步编程。

  区别:协程是一种轻量级的线程,运行在线程之上。相比线程,协程消耗的资源较少,因为每个线程都有自己的堆栈和上下文,而协程是运行在用户空间的,不需要保存上下文信息。线程在等待某种资源或者等待I/O操作完成时,会被阻塞,并且在阻塞的期间还一直霸占着CPU资源。而协程的挂起是不会阻塞线程的,运行在这个线程上的其他协程还会照常执行,并且协程挂起时会主动释放自己的CPU资源。

三.Kotlin协程的基础设施

  Kotlin的协程实现分为两个层次:

  • 基础设施层:标准库的协程API,主要对协程提供了概念和语义上最基本的支持
  • 业务框架层:协程的上层框架支持,也就是在基础设施层的基础上再封装一层

  为了便于区分,我们将Kotlin协程的基础设施层创建的协程称为简单协程,将基于业务框架层创建的协程称为复合协程,这一小节主要来讨论简单协程的使用。

  (1)简单协程的创建

val continuation=suspend{
        println("协程体内")
        "Hello Coroutine"
    }.createCoroutine(object:Continuation<String>{
        override val context: CoroutineContext
            get() = EmptyCoroutineContext
        override fun resumeWith(result: Result<String>) {//协程挂起后,在恢复执行时会执行该函数
            println("协程运行结束,结果为:$result")
        }
    })

  标准库提供了一个createCoroutine函数,我们可以使用它来创建协程,但是创建的协程不会立马执行。我们先来看看它的声明:

public fun <T> (suspend () -> T).createCoroutine(
    completion: Continuation<T>
): Continuation<Unit>

  其中suspend ()->T是一个被suspend修饰的挂起函数,这也是协程的执行体,不妨称作协程体

  参数completion会在协程执行完成后调用,也就是协程的完成回调

  函数的返回值是一个Continuation对象,其实也是指我们的协程体,只是套上了一层壳,协程挂起后的恢复执行,就是由它负责的

  (2)协程的启动

  调用continuation.resume(Unit)之后,协程体会立即执行。

  不过一般来说,协程创建完成之后,我们会要求它立马执行,因此标准库也提供了一个一步到位的函数:

public fun <T> (suspend () -> T).startCoroutine(
    completion: Continuation<T>
) {
    createCoroutineUnintercepted(completion).intercepted().resume(Unit)
}

  启动上面创建的协程,会得到返回值:

  {协程体内
  协程运行结束,结果为:Success(Hello Coroutine)}

  也就是说,协程体的返回值会作为resumeWith的参数传入,如本例中就得到Success(Hello Coroutine)

  (3)协程体的Receiver

  协程的创建和启动一共有两组api,另一组api如下:

public fun <R, T> (suspend R.() -> T).createCoroutine(
    receiver: R,
    completion: Continuation<T>
): Continuation<Unit>

public fun <R, T> (suspend R.() -> T).startCoroutine(
    receiver: R,
    completion: Continuation<T>
) {
    createCoroutineUnintercepted(receiver, completion).intercepted().resume(Unit)
}

  仔细观察可以发现,就是多了一个receiver参数,这个receiver参数可以为协程体提供一个作用域,在协程体内我们可以直接使用作用域内提供的函数或者状态等。

  为了方便使用带有Receiver的的协程api,我们封装一个用来启动协程的函数launch:

fun <R,T> launch(receiver:R,block:suspend R.()->T){
    block.startCoroutine(receiver,object:Continuation<T>{
        override val context: CoroutineContext
            get() = EmptyCoroutineContext

        override fun resumeWith(result: Result<T>) {
            println("Coroutine end:$result")
        }
    })
}

  使用时先创建一个作用域,可以定义一个CoroutineScope类来创建一个协程的作用域,代码如下:

class CoroutineScope{
    suspend fun produce(){
        println("生产一个产品")
    }
}

fun main() {
    launch(CoroutineScope()){
        produce()//直接使用作用域内提供的函数
        delay(1000)
    }
}

  作用域可以用来提供函数支持,自然也可以用来增加限制。如果我们为Receiver对应的类型增加一个RestrictsSuspension注解,那么在它的限制下,在协程体内就不能调用外部的挂起函数了,也就是说如果调用delay函数就会出错。

  (4)函数的挂起

  我们已经知道使用suspend关键字可以声明一个挂起函数,挂起函数只能在协程体内或其他挂起函数中调用。这样一来,整个kotlin语言体系就可以分为两派:普通函数和挂起函数。其中挂起函数中可以调用任何函数,普通函数中只能调用普通函数。

  但是,需要注意的是,挂起函数不一定真的会挂起,只是提供了挂起的条件。那什么时候才会挂起呢?在回答这个问题之前我们先来了解一个概念:挂起点,在协程内部挂起函数的调用处被称为挂起点,只有当挂起点处发生异步调用,当前协程才会被挂起,直到这个协程对应的continuation实例的resumeWith函数被调用时才会恢复执行。

  (5)协程的上下文

  协程的上下文用于提供协程启动和执行时所需要的信息,它是一个特殊的集合类型,有点像Map,集合中每个元素都是Element,并且有一个Key与之对应,Element之间可以通过"+"连接起来。主要有4种类型的Element:

  • Job:协程的唯一标识,用来管理协程的各个生命周期(new ,active,completing,completed,cancelling,cancelled)
  • CoroutineDispatcher:协程调度器,用于指定协程运行在哪个线程(IO,Main,Default,Unconfined)
  • CoroutineName:指定协程的名称
  • CoroutineExceptionHandler:指定协程的异常处理器,用来处理未捕获的异常

  协程的标准库也为我们定义了一个空的协程上下文,EmptyCoroutineContext,里面没有任何数据。

  (6)协程的拦截器

  我们现在已经知道Kotlin协程可以通过调用挂起函数实现挂起,可以通过Continuation的恢复调用实现恢复,还知道协程可以通过绑定一个上下文来设置一些数据来丰富协程的能力,那么我们最关心的问题来了,协程如何处理线程的调度?答案就是通过拦截器,它可以拦截协程异步回调时的恢复调用,那么想要操纵线程的调度应该不是什么难事。

  挂起点的恢复执行的位置可以添加拦截器来实现一些切片操作,定义拦截器只需要实现拦截器的接口,并添加到相应的协程上下文中即可。

class LogInterceptor(override val key: CoroutineContext.Key<*>) :ContinuationInterceptor{
    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        println("恢复调用前")
        continuation.resumeWith(Result.success("恢复调用" as T))
        println("恢复调用后")
        return continuation
    }
}

  这样一来,协程在挂起完执行恢复调用的前后就会打印出上面的日志,除了打印日志外,拦截器含有其他作用,调度器就是基于拦截器实现的,这块内容后面再提。

  

标签:调用,协程,函数,Kotlin,线程,Continuation,fun,系列
From: https://www.cnblogs.com/luqman/p/coroutine1.html

相关文章

  • 反转链表系列问题
    反转链表系列问题作者:Grey原文地址:博客园:反转链表系列问题CSDN:反转链表系列问题反转单链表题目描述见:LeetCode206.ReverseLinkedList思路如下对于任何一个节点cur来说,记录一个前驱节点pre(第一个节点的前驱节点是null)先用一个临时节点tmp记录cur的下一个......
  • 假期没闲着系列 续作 02
    今天下班早(呸,之后每天都得下班早点),然后有个项目有依赖需要内网下载,不过看了下里面的东西大概都还是用过的。略。项目管理工具,(TaskSaas),我还听朋友圈一位说他们公司自研的叫taskc,这个又进行了更新,加入了在每天0点30分将当天的deadline的task推给assign和attentions,放到了提醒的左侧做......
  • Java下跌,Kotlin闯进前15,后生可畏
    近年来,Android开发由Java转Kotlin似乎成为了一种潮流。谷歌甚至曾公开表示:“Android的开发将越来越以Kotlin为先。”当前,作为移动开发中Java的劲敌,Kotlin在Tiobe流行指数中表现强劲。根据TIOBE11月发布的编程语言排行榜,Kotlin以1.15%的占比位列第15,较之10月上升3位。而在今......
  • 【笔记】C++系列02:连续的作用域解析运算符::的场景有哪些?
    在C++中,可以使用连续的作用域解析运算符::来访问嵌套的命名空间、类和类成员。这种用法通常在以下场景下出现:命名空间嵌套:当命名空间中存在嵌套的命名空间时,可以使用连续的作用域解析运算符来访问内层命名空间中的成员。例如:namespaceA{namespaceB{namespac......
  • 使用Python协程并发测试cdn响应速度
    代码干净清爽才能看着赏心悦目:#!/usr/bin/envpython3.11importtimefromcontextlibimportcontextmanagerfromenumimportStrEnumimportanyioimporthttpx@contextmanagerdeftimeit(msg:str):start=time.time()yieldcost=time.time()-sta......
  • 数据库系列:RR和RC下,快照读的区别
    数据库系列:MySQL慢查询分析和性能优化数据库系列:MySQL索引优化总结(综合版)数据库系列:高并发下的数据字段变更数据库系列:覆盖索引和规避回表数据库系列:数据库高可用及无损扩容数据库系列:使用高区分度索引列提升性能数据库系列:前缀索引和索引长度的取舍数据库系列:MySQL引擎My......
  • 《最新出炉》系列初窥篇-Python+Playwright自动化测试-32-JavaScript的调用执行-下篇
    1.简介 在实际工作中,我们需要对处理的元素进行高亮显示,或者有时候为了看清楚操作过程和步骤我们需要跟踪鼠标点击了哪些元素需要标记出来。虽然很少遇到,但是为了以后大家可以参考或者提供一种思路,今天宏哥就在这里把这种测试场景playwright是如何处理的讲解和分享一下。2.用法......
  • 普冉PY32系列(十二) 基于PY32F002A的6+1通道遥控小车III - 驱动篇
    目录普冉PY32系列(一)PY32F0系列32位CortexM0+MCU简介普冉PY32系列(二)UbuntuGCCToolchain和VSCode开发环境普冉PY32系列(三)PY32F002A资源实测-这个型号不简单普冉PY32系列(四)PY32F002A/003/030的时钟设置普冉PY32系列(五)使用JLinkRTT代替串口输出日志普冉P......
  • Kotlin Notes - 4
    Ahigher-orderfunctionisafunctionthattakesfunctionsasparameters,orreturnsafunction.fun<T,R>Collection<T>.fold(initial:R,combine:(acc:R,nextElement:T)->R):R{varaccumulator:R=initialfor(elem......
  • 普冉PY32系列(十) 基于PY32F002A的6+1通道遥控小车I - 综述篇
    目录普冉PY32系列(一)PY32F0系列32位CortexM0+MCU简介普冉PY32系列(二)UbuntuGCCToolchain和VSCode开发环境普冉PY32系列(三)PY32F002A资源实测-这个型号不简单普冉PY32系列(四)PY32F002A/003/030的时钟设置普冉PY32系列(五)使用JLinkRTT代替串口输出日志普冉P......