kotlin之协程的理解与使用详解

前言

        为什么在kotlin要使用协程呢,这好比去了重庆不吃火锅一样的道理。协程的概念并不陌生,在python也有提及。任何事务的作用大多是对于所依赖的环境相应而生的,协程对于kotlin这门语言也不例外。协程的优点,总的来说有如下几点:轻量级,占用更少的系统资源; 更高的执行效率; 挂起函数较于实现Runnable或Callable接口更加方便可控; kotlin.coroutine 核心库的支持,让编写异步代码更加简单。当然在一些不适应它的用法下以上优势也会成为劣势。

1.协程定义

协程定义:kotlin官方基于JVM的线程实现的一个并发任务处理框架,封装的线程api

  1. 使用方便,不使用回调实现线程切换,使用同步方式写出异步代码
  2. 所有的耗时任务保证一定放在后台执行挂起
  3. 函数执行完毕之后,协程会把它切换到原先的线程的线程。

2.协程的基本用法

常规函数中一般都有:call and return,协程在此之外添加了suspend和resume.

  1. suspend 用于暂停执行的当前协程,并保存所有的局部变量
  2. resume 用于已暂停的协程中暂停出恢复

supend(挂起函数)是什么,有什么意义

suspend,对协程的挂起并没有实际作用,其实只是一个提醒,函数创建者对函数的调用者的提醒,提醒调用者我是需要耗时操作,需要用挂起的方式,在协程中使用.

  1. 需要注意的是挂起函数只能在挂起函数或者协程作用域中使用,为什么挂起函数需要在协程作用域中使用?因为普通函数没有suspend和resume这两个特性,所以必须要在协程的作用中使用。
  2. 意义:
  3. 语法层面:作为一个标记和提醒。通过报错来提醒调用者和编译器,这是一个耗时函数,需要放在后台执行。
  4. 编译器层面:辅助 Kotlin 编译器来把代码转换成 JVM 的字节码。
  5. 怎么自定义suspend函数?
  6. 什么时候定义?
  7. 需要耗时操作的时候,需要定义,例如io耗时操作(请求网络);获取数据库数据;一些等待一会需要的操作;列表排除,json解析等;
  8. 怎么写suspend函数,给函数前加上suspend 关键字,把内容用withContext包起来
suspend fun testSuspendfun(){
      withContext(Dispatchers.IO){

      }
  }

协程如何确保主线程安全

  1. Dispatchers.Main 调用程序在Android的主线程中
  2. Dispatchers.IO 适合主线程之外的执行磁盘或者网络io操作,例如文件的读取与写入,任何的网络请求
  3. Dispatcher.Default 适合主线程之外的,cpu的操作,例如json数据的解析,以及列表的排序,

协程的挂起本质:本质就是切线程,完成之后只不过可以自动切回来

协程挂起就是切个线程,在挂起函数执行完毕之后,协程会自动的重新切回它原先的线程,也就是稍后会被切回来的线程切换。切回来就是resume,恢复功能是协程,所以suspend函数需要在另一个suspend函数或者协程中调用。「非阻塞式挂起」阻塞的方式写出了非阻塞的方式。

3.协程的创建以及取消

//创建一个协程

Val scope = CoroutineScope(Dispatchers.Main+Job())

通过Job获取协程的生命周期

scope.launch{
}

其他耗时请求,例如从数据库中获取数据

 scope.async {
 }

在KTX库为某些生命周期提供自己的CoroutineScope,例如ViewModel中viewModelScope,Lifecycle有lifecycleScope

协程的启动,launch 启动新协程而不将结果返回给调用方

//创建之后,不管后续
launch(){
}

async 启动一个新协程,并通过deferred的await方法暂停函数

//返回deferred 对象
val deferred async{

}
deferred.await()

协程的结构化并发,取消协程

协程的结构化并发,可以让协程非常便于管理。例如在关闭activity中要取消协程。如果是在线程中,取消所有的线程比较复杂。

取消父协程以及父里面的子协程

  val scope = CoroutineScope(Dispatchers.Main+ Job())
        scope.launch {
            val job = launch {

               val job1 =  launch {

                }
            }
            job.cancel()
        }
        scope.cancel()

取消子协程某一个,每一个协程都会返回一个job对象,通过调用job的cancle,可以去取消单个的协程的。

val scope = CoroutineScope(Dispatchers.Main+ Job())
        scope.launch {
            val job = launch {

               val job1 =  launch {

                }
            }
            job.cancel()
        }
        scope.cancel()

4.协程中异常处理

  在协程内部中捕获异常

val scope = CoroutineScope(Dispatchers.Main+ Job())
        scope.launch {
            try {

            }catch (e:Exception){
            }
        }

5.协程的优势

        在程序运行过程中某些操作(像是:网络IO、文件IO、CPU或GUP计算密集型工作等等)可能会耗费大量的时间,在单线程的环境下可能会造成线程的阻塞,在他们完成之前没办去做其它事情。使用传统方法的话,我们可能会选择使用多线程来解决这个问题,将这些耗时操作放置到新的线程中去执行,使主线程能够正常的运行。那么本文标题所提到的协程是怎么一回事呢?

        协程可以看作是一个轻量级的线程,他不是由操作系统或是虚拟机来实现的,而是通过编译器。这意味着相对于线程,协程的开销更小。大家可以从下面的这个例子中感受一下。

        下面是一段Kotlin使用协程的代码,创建了100万个协程 (官方的例子是使用的100K,不过运行时间太短,不好截内存的使用情况)。

fun main(args: Array)= runBlocking {    val jobs= List(1_000_000){
        launch(CommonPool){
            delay(10L)
            println(it)
       }
    }
    jobs.forEach { it.join() }
}

内存使用情况

运行耗时:

然后是使用线程来进行实现的代码:

fun main(args: Array) {    val threadList=List(1_000_000){
        Thread{
            Thread.sleep(10L)
            println(it)
        }
    }
    threadList.forEach { it.start();it.join() }
}

内存使用情况:

运行耗时:10分钟以上。

        使用线程的代码,占用的内存几乎是使用协程的两倍。而且从运行时间上看使用协程实现的程序话费的时间要远远低于线程的实现方式。单从这两点来看,协程拥有更高的执行效率,占用更少的系统资源。那么Kotlin中的协程是通过什么来实现异步操作的呢?它使用的是一种叫做 挂起 的机制。协程的挂起几乎是没有损耗的,换种说法,就是不需要选择额外的上下文或是操作系统调用。 另外一点, 挂起能很大程度上被用户库给控制:我们可以决定在挂起状态下具体做些什么,并且围绕着需求进行优化/日志/拦截等操作。

        协程不能随随便便就被挂起,只能在一个称为挂起点的地方,在这里会去调用特别标记的函数。这样的函数被称作 挂起函数,因为你调用他们会挂起一个协程(如果允许这次调用的话,库可以直接进行处理而不需要挂起)。 挂起函数的声明需要添加suspend修饰符。例如:

suspend fun doSomething(foo: Foo): Bar {
...
}

        挂起函数就像平常使用的函数一样,可以有参数和返回值,但是他们只能被协程或是其它挂起函数调用。事实上,要想启动一个协程,至少得有一个挂起函数,并且一般是匿名的(也就是一个挂起lambda表达式)。

        线程往往是没有返回值(实现Runnable接口),尽管可以通过实现Callable接口来获得带返回值的线程。但这与协程在语法层面上的支持,在使用的便捷性上还是有不少差距的。

        协程是通过编译技术实现的 (不需要虚拟机或操作系统的特别支持),这一点在开头也提到了。挂起操作通过代码变换实现。基本上,每一个挂起函数(可能会进行优化,但我们在着不想讨论这点)都被转换成一个状态机,那些状态与挂起调用相对应。在一个挂起准备好之前,下一状态与相关局部变量等一起存储在编译器生成的类的字段中。在恢复该协程时,恢复局部变量并且状态机从刚好挂起之后的状态进行。挂起的协程可以作为保持其挂起状态与局部变量的对象来存储和传递。

        许多其它语言实现的异步机制也能制作成库,在Kotlin的协程中使用。包括:C#和ECMAScript写的 async/await , channels Go语言写的 select ;C#和Python写的 generators/yield 。

到此这篇关于kotlin之协程的理解与使用详解的文章就介绍到这了,更多相关kotlin之协程内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Kotlin常用函数let,with,run,apply用法与区别案例详解

    在kotlin编程中let.with.run.apply这些函数使用率是非常高的,有时候可以通用,差别很小,但如果能记住他们的不同点,可以更加合理的选择使用. 在这之前首先要了解一下Lambda表达式的一些规则,这会帮助你理解使用这些函数的时候有没有( )可不可以用it代替参数等.因为这些函数的最后一个参数都是lambda. 如何理解lambda呢?可以把lambda理解为就是一个对象,但这个对象比较特殊,它是一段代码,既然是对象就可以作为函数的参数使用.这种对象称为函数对象. lambda表达

  • Kotlin lateinit与by lazy案例详解

    lateinit 和 lazy 是 Kotlin 中的两种不同的延迟初始化的实现 lateinit 只用于变量 var,而 lazy 只用于常量 val lazy 应用于单例模式(if-null-then-init-else-return),而且当且仅当变量被第一次调用的时候,委托方法才会执行. lazy()是接受一个 lambda 并返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并

  • Kotlin如何安全访问lateinit变量的实现

    Kotlin设计之初就是不允许非null变量在声明期间不进行初始化的,为了解决这个问题,Kotlin lateinit 允许我们先声明一个变量,然后在程序执行周期的将来某个时候将其初始化,让编译检查时不会 因为属性变量未被初始化而报错.如果未初始化将导致以下异常: kotlin.UninitializedPropertyAccessException: lateinit property mList has not been initialized 所以我们在 Kotlin 1.2及更高版本上,

  • Kotlin修饰符lateinit(延迟初始化)案例详解

    Kotlin定义变量一般有如下写法 lateinit var name: String var age: String? = null 那么用lateinit 修饰和下面那种有什么区别呢,我们来看一下这两行代码反编译成java代码是什么样子的. @NotNull public String name; @Nullable private String age; @NotNull public final String getName() { String var10000 = this.name

  • kotlin之协程的理解与使用详解

    前言         为什么在kotlin要使用协程呢,这好比去了重庆不吃火锅一样的道理.协程的概念并不陌生,在python也有提及.任何事务的作用大多是对于所依赖的环境相应而生的,协程对于kotlin这门语言也不例外.协程的优点,总的来说有如下几点:轻量级,占用更少的系统资源: 更高的执行效率: 挂起函数较于实现Runnable或Callable接口更加方便可控: kotlin.coroutine 核心库的支持,让编写异步代码更加简单.当然在一些不适应它的用法下以上优势也会成为劣势. 1.协程

  • Kotlin协程launch启动流程原理详解

    目录 1.launch启动流程 反编译后的Java代码 2.协程是如何被启动的 1.launch启动流程 已知协程的启动方式之一是Globalscope.launch,那么Globalscope.launch的流程是怎样的呢,直接进入launch的源码开始看起. fun main() { coroutineTest() Thread.sleep(2000L) } val block = suspend { println("Hello") delay(1000L) println(&q

  • 关于Python核心框架tornado的异步协程的2种方法详解

    什么是异步? 含义 :双方不需要共同的时钟,也就是接收方不知道发送方什么时候发送,所以在发送的信息中就要有提示接收方开始接收的信息,如开始位,同时在结束时有停止位 现象:没有共同的时钟,不考虑顺序来了就处理 直观感受:就是不用等了,效率高 同步 含义:指两个或两个以上随时间变化的量在变化过程中保持一定的相对关系 现象:有一个共同的时钟,按来的顺序一个一个处理 直观感受 :就是需要等候,效率低下 那么今天我们看怎么用2种方法用代码实现tornado的异步? 这些是导入的包: 2种方法用代码实现to

  • Python 异步协程函数原理及实例详解

    这篇文章主要介绍了Python 异步协程函数原理及实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一. asyncio 1.python3.4开始引入标准库之中,内置对异步io的支持 2.asyncio本身是一个消息循环 3.步骤: (1)创建消息循环 (2)把协程导入 (3)关闭 4.举例: import threading # 引入异步io包 import asyncio # 使用协程 @ asyncio.coroutine def

  • Python协程的用法和例子详解

    从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数.可是,在协程中, yield 通常出现在表达式的右边(例如, datum = yield),可以产出值,也可以不产出 -- 如果 yield 关键字后面没有表达式,那么生成器产出 None. 协程可能会从调用方接收数据,不过调用方把数据提供给协程使用的是 .send(datum) 方法,而不是next(-) 函数. ==yield 关键字甚至还可以不接收或传出数据.不管数据如何流动, yield 都是一种流程控制工具,使用

  • Swoole4.4协程抢占式调度器详解

    前言 Swoole内核团队开设的专栏,会逐渐投入精力写文章介绍Swoole的开发历程,实现原理,应用实践等,大家可以更好的交流,共同学习,建设PHP生态. 协程调度 去年Swoole推出了4.0版本后,完整的支持PHP协程,我们可以基于协程实现CSP编程,身边的开发者惊呼,原来PHP代码还可以这样写.Swoole的协程默认是基于IO调度,程序中有阻塞会自动让出当前协程,协程的各种优势我们不在这里展开讨论.如果是IO密集型的场景,可以表现得很不错.但是对于CPU密集型的场景,会导致一些协程因为得不

  • 利用Kotlin的协程实现简单的异步加载详解

    前言 众所周知在android中当执行程序的耗时超过5秒时就会引发ANR而导致程序崩溃.由于UI的更新操作是在UI主线程进行的,理想状态下每秒展示60帧时人眼感受不到卡顿,1000ms/60帧,即每帧绘制时间不应超过16.67ms.如果某项操作的耗时超过这一数值就会导致UI卡顿.因此在实际的开发中我通常把耗时操作放在一个新的线程中(比如从网络获取数据,从SD卡读取图片等操作),但是呢在android中UI的更新只能在UI主线程中进行更新,因此当我们在非UI线程中执行某些操作的时候想要更新UI就需

  • Kotlin中协程的创建过程详析

    目录 为什么需要协程? 创建并启动协程 协程的执行过程 suspend block 是如何变为协程体被执行的? 总结 总结 为什么需要协程? 协程可以简化异步编程,可以顺序地表达程序,协程也提供了一种避免阻塞线程并用更廉价.更可控的操作替代线程阻塞的方法 – 挂起函数. Kotlin 的协程是依靠编译器实现的, 并不需要操作系统和硬件的支持.编译器为了让开发者编写代码更简单方便, 提供了一些关键字(例如suspend), 并在内部自动生成了一些支持型的代码. 创建并启动协程 fun create

  • 对Python协程之异步同步的区别详解

    一下代码通过协程.多线程.多进程的方式,运行代码展示异步与同步的区别. import gevent import threading import multiprocessing # 这里展示同步和异步的性能区别,可以看到异步直接同时执行并完成, # 而同步,需要等待第一个完成后再次执行下一个,是有顺序的执行,而异步不需要 import time def task(pid): gevent.sleep(0.5) print('Task %s done' % pid) def task2(pid)

  • Docker的理解和基本命令详解

    如何通俗解释D ocker是什么? Docker思想来自于集装箱,集装箱解决了什么问题呢?比如,在一艘大船上,要把各种各样的货物要整理起来,集装箱(Docker)就可以做到,并且相互间不会影响.就不需要指定运输的船了(这个船运吃的那个船运穿的).只要把货物装在集装箱里封装好,就可以用一艘大船把他们都运走. 1.Docker就是类似的理念.云计算是运输船,Docker就是集装箱. 1.不同的应用程序可能会有不同的应用环境,比如.net开发的网站和php开发的网站依赖的软件就不一样,如果把他们依赖的

随机推荐