Kotlin协程

探索 Kotlin 协程原理 接下来跟大家分享一下我在了解 Kotlin 协程实现的过程中理解的一些概念,如果你发现哪些地方我说错了的话,欢迎提出你的理解。 1. Kotlin 协程原理概述 Kotlin 协程的大致的执行流程如上图所示,这个流程是各种类型的协程执行时都大致遵循的流程,不是一个严格精确的执行流程。 下面先来看下协程执行过程中的一些关键类的介绍。 1. 协程代码块 当我们调用 launch() 或 withContext() 等协程构建器函数时,对应的协程代码块会被 Kotlin 编译器转换为一个匿名内部类,这个匿名内部 阅读详情

协程是什么?

协程本质上是一个轻量级的线程

如何使用?

针对不同语言和平台使用方式可参考**在项目中使用Kotlin协程**

  • Android中使用配置

    1. 在app->build.gradle中引入

      implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6'
      
  • 具体使用

    使用GlobalScope的launch构建器来构建顶层协程

    class Test{
    	fun Fun1(){
        //创建协程并继续
        GlobalScope.launch{
    	         delay(1000L)       //模拟耗时(非阻塞等待1s)
               println("World")   //延迟1s后打印输出
    	    }
        println("Hello")          //协程已在等待(非阻塞),主线程还在继续
        Thread.sleep(2000L)       //主线程阻塞2秒保证JVM存活
    	}
    }
    //代码运行结果
    Hello
    World
    

    以上代码中,我们混用了协程非阻塞的delay(...)与阻塞的Thread.sleep(...)。这容易让我们记混哪个是阻塞哪个是非阻塞的。因此我们通过显示使用runBlocking协程构建器来实现阻塞

    fun Fun1(){
        //创建协程并继续
        GlobalScope.launch{
    	         delay(1000L)       //模拟耗时(非阻塞等待1s)
               println("World")   //延迟1s后打印输出
    	    }
        println("Hello")          //协程已在等待(非阻塞),主线程还在继续
        //Thread.sleep(2000L)     //主线程阻塞2秒保证JVM存活
        runBlocking{
          delay(2000L)  //通过runBlocking协程构建器实现显示阻塞,效果等同于Thread.sleep
        }
    	}
    

    上述代码我们通过阻塞主线程延迟一段时间来等待协程运行结束,显然不是一个好选择,我们通过显示(非阻塞方式)等待所启动的后台job执行结束

     //创建协程并赋值给一个变量
        val obj = GlobalScope.launch{
    	         delay(1000L)       //模拟耗时(非阻塞等待1s)
               println("World")   //延迟1s后打印输出
    	    }
        println("Hello")          //协程已在等待(非阻塞),主线程还在继续
        obj.join()                //通过调用创建携程对象的join方法,等待直到子协程执行结束。
    现在,结果是一样的,但是无需手动设置阻塞时间。主协程与后台作业的持续时间没有任何关系。
    

结构化并发(runBlocking)

上面我们简单的介绍了协程的使用,当我们使用GlobalScope.launch时,会创建一个顶级协程,虽然轻量但仍会耗费一些资内存资源。如果忘记保持对新启动协程的引用(job = GolbalScope.luanch{}),它还会继续运行。

因此,我们通过在代码中使用解构化并发,在指定的作用域内启动协程执行操作,而不是像通常使用线程(线程总是全局的)那样在GlobalScpde中启动。结构化并发可以保证当一个作用域被取消,作用域里面的所有协程会被取消。

代码示例

我们使用runBlocking协程构建器将main函数转换为协程。包括runBlocking在内的每个协程构建器都将CoroutineScope的实例添加到其代码块所在的作用域中,我们可以在这个作用域内启动协程而无需显示的join(),因为外部协程(runBlocking)直到在其作用域中启动的所有协程都执行完毕才会结束。

注意:runBlocking是创建一个新的协程同时阻塞当前线程,直到协程结束。这个不应该在协程中使用,主要是为main函数和测试设计的

/**
 *通过GlobalScope.launch创建顶级协程
 */
fun main(){
  val job = GlobalScope.launch{    
    delay(1000L)
    println("World")
  }
  println("Hello")
  //通过协程的join方法来确保启动的协程完成操作后结束
  job.join()                      
}

/**
 *优化,通过runBlocking协程构建器将函数转换为协程
 */
fun main() = runBlocking{
  //在runBlocking作用域中创建一个新协程
  launch{     
    delay(1000L)
    println("World")
  }
  println("Hello")
}

runBlocking方法会阻塞当前线程来等待作用域内所有的子协程运行完毕,runBlocking是一个常规函数。

fun test(){
  test1()
  println("main thread")
}

fun test1() = runBlocking{
  launch{
    delay(2000L)
    println("runBlocking launch")
  }
}

//运行结果
runBlocking launch
main thread

在主线程中调用runBlocking协程函数,协程函数内延迟了两秒后才执行主线程中指令,很显然runBlocking确实阻塞了当前线程。

作用域构建器(CoroutineScope)

除了由不同构建器(比如:runBlocking)提供协程作用域之外,还可以使用coroutineScope构建器声明自己的作用域。它会创建一个协程作用域并且在所有已启动的子协程执行完毕之前不会结束。

runBlocking与coroutineScope可能看起来很类似,因为它们都会等待其协程体以及所有子协程结束,两者的主要区别在于:

  • runBlocking方法会阻塞当前线程来等待。是一个常规函数;
  • coroutineScope只是挂起协程不会阻塞线程,会释放底层线程用于其他用途,是挂起函数
fun test(){
  test1()   
  //④
  println("main thread")   
}

//runBlocking协程构建器会阻塞当前线程
fun test1() = runBlocking {
  launch{
     println("runBlockig launch1")
   }
  println("runBlocking1")
  /**
   *coroutineScope 挂起函数不会阻塞当前线程,执行完coroutine Scope1之后,挂起该协程,释放线程使其
   *返回到runBlocking继续执行打印runBlockig launch1
   */
  coroutineScope{  
    launch{
      //②
      println("coroutine Scope launch1")   
    }
    //①
    println("coroutine Scope1")  
  }
  
  //③ 上面coroutineScope挂起函数完全执行结束后,才会继续后续指令
  println("runBlocking2")
  
  
  //重复上述代码块(再启动一个新协程和一个挂起函数,执行逻辑和上面分析一致)
  launch{
     println("runBlockig launch2")
  }
  coroutineScope{  
    launch{
      println("coroutine Scope launch2")   
    }
    println("coroutine Scope2")  
  }
}

//运行结果
runBlocking1
coroutine Scope1
runBlockig launch1
coroutine Scope launch1
runBlocking2
coroutine Scope2
runBlockig launch2
coroutine Scope launch2
main thread

代码执行逻辑分析:

  • ①—>launch不允许线程执行runBlocking快之后指令 ,但允许执行此launch块之后的指令,这就是coroutine Scope1在runBlockig launch1之前打印原因;
  • ②—>在runBlocking上下文中嵌套的coroutineScope挂起函数,当线程遇到coroutineScope挂起函数时,会挂起当前协程,释放底层线程,使线程回到runBlocking代码中,因此runBlockig launch1在coroutine Scope launch1之前打印;
  • ③—>在runBlocking上下文中嵌套的coroutineScope挂起函数,当线程遇到coroutineScope挂起函数时,将不允许线程执行此coroutineScope挂起函数块之后的指令,因此runBlocking2总是在coroutine Scope launch1之后打印。
  • ④—>runBlocking阻塞当前调用它的线程,直到内部协程完成为止。因此main thread在test1()函数执行结束之后最后才会被打印;

提取函数重构(suspend)

我们将launch{...}内部的代码块提取到独立的函数中,你会得到一个带有suspend修饰符的新函数,即挂起函数。在协程内可以像普通函数一样使用挂起函数

fun test() = runBlocking{
  launch{
    doWork()   //调用挂起函数
  }
  
  launch{
    delay(1000L)
    println("funBlocking launch1")
    coroutineScope{
      println("coroutineScope1")
      delay(2000L)
      println("coroutineScope2")
    }
  }
}

//将launch内代码块提取到独立函数中,成为一个挂起函数
suspend fun doWork(){
  delay(2000L)
  println("to to ...")
}

suspend修饰符特点

  • 用于修饰函数(表示该函数为挂起函数)
  • 不会造成线程阻塞(会挂起协程)
  • 只能在协程中使用
fun main() = runBlocking{
  //启动协程1
  launch{
    println("runBlocking launch1")
  }
  //启动协程2
  launch{
     println("runBlocking launch2")
  }
  //调用挂起函数,不会造成线程阻塞,会挂起协程
  test()
}
suspend fun test(){
  println("befor delay in suspend function")
  delay(2000L)    //挂起协程
  println("after delay in suspend function")
}

//运行结果
befor delay in suspend function
runBlocking launch1
runBlocking launch2
after delay in suspend function

代码解释

在runBlocking上下文中嵌套的挂起函数,当线程遇到挂起函数时,会挂起当前协程,释放底层线程,使线程回到runBlocking代码中,因此runBlocking launch1和runBlocking launch2在after delay in suspend function之前打印;

取消与超时(cancelAndJoin&withTimeOut)

取消协程执行

当我们的协程不在需要的时候(比如:用户关闭了一个启动协程的界面,那么协程执行结果已经不再被需要),这时它应该可以被取消,launch返回一个可用来取消运行中的协程job

fun test() =runBlocking{
  val job = launch{
    delay(5000)
    println("Hello")
  }
  delay(2000)          //延迟2s
  job.cancelAndJoin()  //取消并等待结束。等价于job.cancel()与job.join()组合
}
//运行后,延迟两秒后协程被取消,不会打印Hello

注意:如果协程正在执行计算任务,并且没有检查取消的话,那么它是不能被取消的,就如如下示例代码所示:

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.")

以上代码在调用取消后, 作业仍然执行了五次循环迭代并运行到了它结束为止。

如何操作使正在操作的协程可取消呢?可通过CoroutineScope中的扩展属性isActive来实现

//我们将上述的while(i<5)替换为while(isActive),当调用job.canel(),协程会检查当前的状态
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.")

经过使用isActive来使得运行中的协程也可被取消

超时

在取消协程时候,有可能会超时抛出异常的

withTimeout(1300L) {
    repeat(1000) { i ->
        println("I'm sleeping $i ...")
        delay(500L)
    }
}

延迟500毫秒循环1000次,当超过1300毫秒时withTimeOut抛出TimeoutCancellationException。

由于取消只是一个例外,所有的资源都使用常用的方法来关闭。 如果你需要做一些各类使用超时的特别的额外操作,可以使用类似 withTimeout 的 withTimeoutOrNull 函数,并把这些会超时的代码包装在 try {...} catch (e: TimeoutCancellationException) {...} 代码块中,而 withTimeoutOrNull 通过返回 null 来进行超时操作,从而替代抛出一个异常:

以下两段代码运行时不再抛出异常:

val result = withTimeoutOrNull(1300L) {
    repeat(1000) { i ->
        println("I'm sleeping $i ...")
        delay(500L)
    }
}
println("result is $result") 

//运行结果
I'm sleeping 0 ... 
I'm sleeping 1 ... 
I'm sleeping 2 ... 
Result is null

结合try…catch捕获

val result = withTimeOutOrNull(1300L){
  try{
    repeat(1000){i->
      println("I'm sleeping $i ...")
      delay(500L)
    }
  }catch(e:TimeOutCancellationException){
    println("超时")
  }
}
println("result is $result")

//运行结果
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
超时
result is null

组合挂起函数

在同一个协程中代码执行默认都是顺序(同步)的,比如一个协程内调用两个挂起函数

fun main() = runBlocking {
  val time = measureTimeMillis {
    val num1 = fun1()   //先执行fun1挂起函数,等待执行结束在执行fun2挂起函数
    val num2 = fun2()
    println("num = $num1 + $num2")
    println("完成耗时:$time")
  }
}

//挂起函数1
private suspend fun fun1(): Int {
    delay(2000L)
    println("挂起函数1")
    return 10
}

//挂起函数1
private suspend fun fun2(): Int {
    delay(1000)
    println("挂起函数2")
    return 20
}
//运行结果
挂起函数1
挂起函数2
num = 30
完成耗时:3057
使用async实现异步并发

如果我们想在同一个协程内并发(异步)的运行多个挂起函数怎么办呢?可通过**async**实现并发

async类似于launch。它启动了一个单独的协程,这是一个轻量级的线程,并与其他所有的协程一起并发工作。

不同在于:

  • launch返回一个Job并且不附带任何结果值
  • async返回一个Deferred一个轻量级的非阻塞future,代表一个将会在稍后提供结果的promise。可通过使用.await()在一个延迟的值上得到它的最终结果,但是Deferred也是一个Job,所以需要的话,你也可以取消它。

注意:使用协程进行并发总是显式的

使用async实现异步并发

//挂起函数1
private suspend fun fun1(): Int {
   delay(2000L)
   println("挂起函数1")
   return 10
}

//挂起函数2
private suspend fun fun2(): Int {
   delay(1000)
   println("挂起函数2")
   return 20
}

/**
 *使用async实现异步
 */
fun main() = runBlocking{
  val time = measureTimeMillis {
      //通过async启动一个独立的协程来运行挂起函数,进行并发工作,并返回一个Deferred
      val deferred1 = async { fun1() }  
      val deferred2 = async { fun2() }
      //通过deferred的await方法获取延迟执行结果
       println("num = ${deferred1.await() + deferred2.await()}") 
   }
   println("完成耗时:$time")
}
//异步运行结果
挂起函数2
挂起函数1
num = 30
完成耗时:2045

可见,使用async实现一个协程内并发操作多个挂起函数,挂起函数不再是顺序执行,而是并发执行,相比较同步执行耗时明显减少。

注意:async{...}.await()会导致同步,不建议这种使用,如果想实现同步取得结果使用withContext替换

/**
 *使用async{...}.await()导致同步(冗余异步),不建议这种使用
 */
fun main() = runBlocking{
  val time1 = measureTimeMillis {
      //使用async创建一个子协程并直接使用await获取结果,导致asyc变成同步
      val value1 = async { fun1() }.await()  
      val value2 = async { fun2() }.await()
      //通过deferred的await方法获取延迟执行结果
       println("num = ${value1 + value2}") 
   }
   println("完成耗时:$time")
}
挂起函数1
挂起函数2
num = 30
完成耗时:3010

/**
 *优化使用withContext实现同步
 */
fun main() = runBlocking{
  val time1 = measureTimeMillis {
      //使用withContext实现同步
       val value1 = withContext(Dispatchers.Default) { fun1() }
       val value2 = withContext(Dispatchers.Default) { fun2() }
       println("num = ${value1 + value2}") 
   }
   println("完成耗时:$time")
}
挂起函数1
挂起函数2
num = 30
完成耗时:3034

惰性启动async

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

注意:如果设置async(start = CoroutineStart.LAZY)并且通过await方式启动,将会导致挂起函数同步执行,直到await启动的协程程执行结束。这并不是惰性的预期用例。

fun main() = runBlocking{
  //指定async的start为CoroutineStart.LAZY
  val deferred1 = async(start = CoroutineStart.LAZY) { fun1() }
  val deferred2 = async(start = CoroutineStart.LAZY) { fun2() }
  
  //通过start方式启动,然后使用await获取结果
  deferred1.start()
  deferred2.start()
  val num1 = deferred1.await()  
  val num2 = deferred2.await()
  println("num = ${num1 + num2}")
  //运行结果
  挂起函数2
  挂起函数1
  num = 30
  
  //通过await方式直接启动并获取结果(注意:将会导致挂起函数同步执行)
  val num1 = deferred1.await()  
  val num2 = deferred2.await()
  println("num = ${num1 + num2}")
  //运行结果
  挂起函数1
  挂起函数2
  num = 30
}

async风格的函数

上面代码我们通过async{}代码块来执行挂起函数,达到并发目的。与此同时,我们还可以定义异步风格的函数来异步调用挂起函数,并使用async协程构建器并带有一个GlobalScope引用。我们给这样的函数名加上...Async后缀来突出表名异步:事实上该函数只做异步计算,并且需要使用延期的值来获得结果。(在kotlin的协程中强烈不推荐)

注意:这些...Async函数不是挂起函数,它们可以在任何地方使用。然而它们总是在调用它们的代码中意味着异步(并发)

 //async异步风格函数,返回一个Deferred类型
fun fun1Async() = GlobalScope.async {
    fun1()
}

fun fun2Async() = GlobalScope.async {
    fun2()
}

//测试:注意该函数是个普通函数右边没有runBlocking
fun main4() {
    //可以在协程外面调用...Async函数启动异步执行
    val deferred1 = fun1Async()
    val deferred2 = fun2Async()
    //但是等待结果必须放在协程内(其他挂起函数或者阻塞)
    runBlocking {
      val time = measureTimeMillis {
           val num1 = deferred1.await()
           val num2 = deferred2.await()
         	 println("num = ${num1 + num2}")
       }
      print("完成耗时:$time")
    }
}

//运行结果
挂起函数2
挂起函数1
num = 30
完成耗时:2006

协程调度器与线程

协程调度器

所有协程构建器诸如launch、async接收一个可选的CoroutineContext参数,它可以被用来显示的为一个新协程或其上下文元素指定一个调度器

Kotlin中协程调度器有如下几种

  • Dispatchers.Default:协程将会获取默认调度器,使用共享的后台线程池
  • Dispatchers.IO:同Default也是使用共享线程池,区别是IO只定义在JVM平台,Default其他平台也有如js
  • Dispatchers.Unconfined:不局限于任何特定的线程,即在遇到第一个挂起函数前代码运行在原线程中,在执行挂起函数后,运行在子线程中。
  • Dispatchers.Main:协程将运行在UI的主线程中(适用有UI的应用程序Android、JavaFx、Swing)
  • newSingleThreadContext:将使它获得一个新的线程, 在真实的应用程序中两者都必须被释放,当不再需要的时候,使用 close 函数,或存储在一个顶级变量中使它在整个应用程序中被重用
fun main() =runBlocking {
 
   //没有指定CoroutineContext,运行再父协程的上下问中,即runBlocking
	 launch {
      println("main runBlocking working in thread :${Thread.currentThread().name}")
   }

   //指定CoroutineContext为Unconfined不受限的,将工作再主线程中
   launch(Dispatchers.Unconfined) {
      println("Unconfined working in thread :${Thread.currentThread().name}")
   }

   //指定CoroutineContext为Default,将会获取默认调度器
   launch(Dispatchers.Default) {
      println("Default working in thread :${Thread.currentThread().name}")
   }

   //指定CoroutineContext为IO为阻塞任务而设计将,这是工作在共享线程池,
   launch(Dispatchers.IO) {
      println("IO working in thread :${Thread.currentThread().name}")
   }

   //指定为Main,将工作在UI主线程中(如:Android、JavaFx、Swing)
   launch(Dispatchers.Main) {
      println("Main working in thread :${Thread.currentThread().name}")
   }
   
   //指定为newSingleThreadContext,将工作在一个新指定线程中
   launch(customThread){
      println("newSingleThreadContext working in thread :${Thread.currentThread().name}")
   }
}

//运行结果
main runBlocking working in thread :DefaultDispatcher-worker-1
Unconfined working in thread :DefaultDispatcher-worker-2
Default working in thread :DefaultDispatcher-worker-1
IO working in thread :DefaultDispatcher-worker-1
newSingleThreadContext working in thread :MyThread
Main working in thread :main

注意:当调用launch{...}时不传参,它从启动它的CoroutineScope中承袭了上下文(以及调度器)。在这个案例中,它从main线程中的runBlocking主协程承袭了上下文。

在不同的线程间切换(withContext)

协程虽然是微线程,但是并不会和某一个特定的线程绑定,它可以在A线程中执行,并经过某一个时刻的挂起(suspend),等下次调度到恢复执行的时候,很可能会在B线程中执行。

withContext

  1. 与launch、async、runBlocking 类似 withContext 也属于 Coroutine builders协程构造器。不过与他们不同的是,其他几个都是创建一个新的协程,而 withContext 不会创建新的协程。withContext 允许更改协程的执行线程,withContext 在使用时需要传递一个 CoroutineContext 。

  2. withContext 可以有返回值,这一点类似 async。async 创建的协程通过 await() 方法将值返回。而 withContext 可以直接返回。

    fun main2(){
            //创建一个协程,指定运行在Mian线程中
            CoroutineScope(Dispatchers.Main).launch{
                //通过withContext改变协程执行线程为IO
                val task1 = withContext(Dispatchers.IO){
                    delay(2000L)
                    println("模拟耗时网络请求在指定在IO线程中,然后将结果返回给task1")
                    "我是返回的数据"
                }
                println("返回到Mian线程中,获取到task1执行的结果并显示:$task1")
            }
        }
    

withContext 与 async 都可以返回耗时任务的执行结果。 一般来说,多个 withContext 任务是串行(同步)的, 且withContext 可直接返回耗时任务的结果。 多个 async 任务是并行(异步)的,async 返回的是一个Deferred<T>,需要调用其await()方法获取结果

协程异步流(Flow)

使用Flow特点

  • flow是Flow类型构建器函数
  • flow{...}构建块中的代码可以挂起
  • flow构建器声明的函数不再需要使用suspend修饰符
  • 使用emit函数发射值
  • 使用collect函数收集值

基本使用

fun main() = runBlocking{
  //协程内调用Flow构建器函数的collect方法来获取值,间隔1s打印数值
  initData().collect{value->
    println(value)
  }
}

//通过flow构建器创建挂起函数,该函数不需要使用suspend修饰符
fun initData():Flow<Int> = flow{  
  repeat(5){i->
    delay(1000)
    emit(i)  //发射值
  }
}

//运行结果(间隔一秒)
0
1
2
3
4

流构建器

  • flow{}是最基础的一个流构建器

  • flowOf(data)用于构建发射固定值集的流

  • .asFlow()扩展函数,可将各种集合及序列转换为流

//基础流构建器——flow{...}
fun flow1():Flow<Int> = flow{
    for(i in 1..5){emit(i)}
}

//固定值流构建器——flowOf()
fun flow2():Flow<String> = flowOf("Judy","Susan","Tom")

//将集合/数组/可序列对象转换为流——asFlow()
val list:List<Int> = listOf(1,2,3,4)            //集合
val array:Array<Any> = arrayOf("Judy",false,1)  //数组
val interval  =(1..5)                           //区间
fun flow3():Flow<Int> = list.asFlow()
fun flow4():Flow<Any> = array.asFlow()
fun flow5():Flow<Int> = interval.asFlow()

fun testFlow() = runBlocking{
   print("flow{}——>")
   flow1().collect { value -> print("$value,") }
  
   print("\nflowOf()——>")
   flow2().collect { value -> print("$value,") }
  
   print("\nlist.fasFow{}——>")
   flow3().collect { value -> print("$value,") }
  
   print("\narray.fasFow{}——>")
   flow4().collect { value -> print("$value,") }
}

//运行结果
flow{}——>1,2,3,4,5,
flowOf()——>Judy,Susan,Tom,
list.fasFow{}——>1,2,3,4,
array.fasFow{}——>Judy,false,1,
interval.fasFow{}——>1,2,3,4,5,

更改流发射上下文

流构建器flow{}代码运行在相应流收集器(collect)提供的上下文中,不允许从其他上下文中发射(emit)

  • (错误方式)使用withContext更改流发射的上下文

    //通过flow流构建器构建一个流
    private fun testFlow2():Flow<Int> = flow{
            //使用withContext切换指定上下文(错误方式!!!)
            withContext(Dispatchers.Default){
                repeat(3){
                    delay(1000L)
                    emit(it)
                }
            }
        }
    
    fun main() = runBlocking{
      testFlow2().collect{value->println(value)}
    }
    
    //运行结果(在flow{}代码块内,不允许从其他上下文发射)
    Exception in thread "main" java.lang.IllegalStateException: Flow invariant is violated:
    
  • (正确方式)使用flowOn操作符更改流发射的上下文

    //通过在flow{}流构建器尾部使用flowOn(CoroutineContext)来更改流发射的上下文
    private fun testFlow3(): Flow<Int> = flow {
            repeat(3) {
                delay(1000L)
                emit(it)
            }
    }.flowOn(Dispatchers.Default)
    
    //运行结果
    0
    1
    2
    

流操作符

  • transform:流转换操作符,可用来模仿简单转换(如map、filter)和实施更复杂的转换。使用transform操作符,我们可以发射(emit)任意值任意次

    fun main() = runBlocking{
      (1..3).asFlow() 												  //一个经区间转换后的流
            .transform{request->                //使用transform操作符对流进行转换
               emit("Making request $request")  //第一次发射
               emit(performRequest(request))    //第二次发射  
             }
             .collect{response->println(response)}
    }
    //挂起函数,对传入参数进行转换,内部延迟1s
    suspend fun performRequest(request:Int):String{
      delay(1000)
      return "response $request"
    }
    
    //运行结果
    Making request 1
    response 1
    Making request 2
    response 2
    Making request 3
    response 3
    
  • 末端操作符(collect、toList、toSet、first、reduce)

    • collect:是最基本的末端操作赋,收集emit发射的值
    • toList:将流转换为List集合(元素可重复)
    • toSet:将流转换为set集合(元素不可重复)
    • first:获取流中第一个值
    • reduce:累加函数,第一个参数是用来叠加的返回值,第二个参数是本次循环中列表的值
    val interval = (1..5)
    
    fun main() = funBlocking{
      //collect操作符,收集发射的值
      interval.asFlow().collect{value->print("$value,")}  //1,2,3,4,5
      
      //toList操作符,将流转换为List集合
      val list = interval.asFlow().toList()
      println(list)   //[1,2,3,4,5]
      
      //toSet操作符,将流转换为Set集合
      val array:Array<Int> = arrayOf(1,2,3,3,4,4,5)
      val setData = array.asFlow().toSet()
      println(setdata)   //[1,2,3,4,5]
      
      //first操作符,获取流第一个值
      val value = interval.asFlow().first()
      println(value)     //1
      
      //reduce操作符,对流元素进行累加
      val sum = interval.asFlow().reduce{sum,i->sum+i}   
      println(sum)       //25
      //实际测试reduce不仅可以累加甚至可以任何操作,第一个参数为返回的结果值,第二个参数为本次循环中列表值
      val response = interval.asFlow().reduce(result:Any,i->"response = $i")
       println(response)      //response = 5 
    }
    
  • take:限长操作符,在流触及限制长度的时候会将它的执行取消(只会取限制内的流数据)

    fun initFlowData():Flow<Int> = flow{
      for(i in 1..5){
        delay(1000L)
        emit(i)
      }
    }
    
    fun main() = runBlocking{
      initFlowData()
      				.take(2)   //限长操作符,只取前两个
              .collect{value->println(value)}
    }
    
    //运行结果
    1
    2
    
  • 展平操作符

    • map:遍历每一个元素

      fun main() = runBlocking {
         val list1 = listOf<Int>(1, 2, 3)
         val response = list1.map {
              it * 2    //遍历List,将每个元素乘以2换成的结果放到List中
         }
         println("response = $response")
      }
      //运行结果
      response = [2, 4, 6]
      
    • flatmap:遍历每一个元素,并铺平元素

      先来看看嵌套集合使用map和flatMap转换后的区别

      //初始化集合
      val list  = listOf(listOf(1,2,3),listOf("Judy","SuSan","Tom"))
      

      使用Map操作

      val responseMap = list.map { value ->
                  value   //遍历集合每个元素,不做处理直接返回
      }
      println("responseMap = $responseMap")
      //运行结果(内嵌集合不会铺平)
      responseMap = [[1, 2, 3], [Judy, SuSan, Tom]]
      

      使用flatmap操作

      val responseFlatMap = list.flatMap { value ->value }
      //运行结果,可见flatMap将内嵌集合元素全部铺平在一个List中返回
      //[1, 2, 3, Judy, SuSan, Tom]
      

      以上list.flatMap{value->value}可以被简化为list.flatten(),返回给定集合中所有(内嵌)集合的所有元素的单个列表(铺平)。

      //返回给定集合中所有(内嵌)集合的所有元素的单个列表(铺平)。
      val responseFlatMap = list.flatten()
      //[1, 2, 3, Judy, SuSan, Tom]
      

      或者通过value.asIterable()返回一个迭代器

      val responseFlatMap = list.flatMap{value->value.asIterable()}
      //[1, 2, 3, Judy, SuSan, Tom]
      
    • flatMapConcat{}:接受一个内部流(flow{}构建器创建的挂起函数),等待内部流完成之后再开始收集下一个值

      val list = listOf(1,2,3)
      list.asFlow().flatMapConcat{value->
                      //内部流
        							concatFlowMap(value)
      							}
                   .collect{value->
                       println(value)
                   }
      
      //通过flow{}流构建器,创建一个挂起函数
      fun concatFlowMap(value:Int):Flow<String> = flow{
          emit("delay before $value")
          delay(1000L)     //延迟1s
          emit("delay adter $value")
      }
      
      //运行结果(每次遍历等待内部流执行结束才会收集下一个值)
      delay before 1
      delay after 1
      delay before 2
      delay after 2
      delay before 3
      delay after 3
      
    • flatMapMerge{}:接受一个内部流(flow{}构建器创建的挂起函数),无需等待内部流执行完成,并发收集流发射的值

      val list = listOf(1,2,3)
      list.asFlow().flatMapMerge{value->
                      //内部流
        							mergeFlowMap(value)
      							}
                   .collect{value->
                       println(value)
                   }
      
      //通过flow{}流构建器,创建一个挂起函数
      fun mergeFlowMap(value:Int):Flow<String> = flow{
          emit("delay before $value")
          delay(1000L)     //延迟1s
          emit("delay adter $value")
      }
      
      //运行结果(遍历不会等待内部流执行结束,并发收集所有给定元素)
      delay before 1
      delay before 2
      delay before 3
      
      delay after 1
      delay after 2
      delay after 3
      
    • flatMapLatest:接受一个内部流(flow{}构建器创建的挂起函数),并发收集且发出新流后立即取消先前流的收集。

      val list = listOf(1,2,3)
      list.asFlow().flatMapMerge{value->
                      //内部流
        							mergeFlowMap(value)
      							}
                   .collect{value->
                       println(value)
                   }
      
      //通过flow{}流构建器,创建一个挂起函数
      fun latestFlowMap(value:Int):Flow<String> = flow{
          emit("delay before $value")
          delay(1000L)     //延迟1s
          emit("delay adter $value")
      }
      
      /**
       *运行结果(并发收集流值,当新流(delay before 2)到来,还在等待收集(delay after 1)的流
       *被取消了
       */
      delay before 1
      delay before 2   //取消了delay after 1流的收集
      delay before 3   //取消了delay after 2流的收集
      delay adter 3    //因为后面没有新流到来,因此延迟1s后收集到该流
      
  • 流组合操作符

    • Zip:用于组合两个流中的相关值(注意:会按照最小长度流来组合)

      val list1 = listOf(1,2,3)
      val list2 = listOf("Judy","Susan","Tom","Peter")
      list1.asFlow().zip(list2.asFlow()){list1Value,list2Value->
                        "$list1Value——>$list2Value"   
      						  }.collect{value->
        								println(value)
      							}
      
      //运行结果(会按最小长度流进行组合,超过的流元素被忽略,这里忽略了list2.sFlow流中的'Peter')
      1——>Judy
      2——>Susan
      3——>Tom
      

扩展

可在全局创建的协程: lauch 与 runBlocking

lauch 与 runBlocking都能在全局开启一个协程,但lauch 是非阻塞的 而runBlocking 是阻塞的

  • launch创建协程

    btn.setOnClickListener {
        CoroutineScope(Dispatchers.Main).launch{
            delay(500)     //延时500ms
            Log.e("TAG","1[当前线程为:${Thread.currentThread().name}]")
        }
        Log.e("TAG","2[当前线程为:${Thread.currentThread().name}]")
    }
    //运行结果
    2[当前线程为:main]
    1[当前线程为:main]
    

    从上面运行结果可以看出,通过CoroutineScope.launch开启一个协程,协程体里的任务就会先挂起(suspend),不阻塞线程继续执行后续的代码,让CoroutineScope.launch后面的代码继续执行,直到协程体内的方法执行完成再自动切回来所在的上下文。

  • runBlocking创建协程

    btn.setOnClickListener {
        runBlocking{
            delay(500)    //延时500ms
            Log.e("TAG","1[当前线程为:${Thread.currentThread().name}]")
        }
        Log.e("TAG","2[当前线程为:${Thread.currentThread().name}]")
    }
    //运行结果
    1[当前线程为:main]
    2[当前线程为:main]
    

    runBlocking里的任务如果是非常耗时的操作时,会一直阻塞当前线程,在实际开发中很少会用到runBlocking。由于runBlocking 接收的 lambda 代表着一个 CoroutineScope,所以 runBlocking 协程体内可继续通过launch来继续创建一个协程,避免了lauch所在的线程已经运行结束而切不回来的情况。

可返回结果的协程:withContext 与 async

withContext 与 async 都可以返回耗时任务的执行结果。 一般来说,多个withContext 任务是串行的, 且withContext 可直接返回耗时任务的结果。 多个 async 任务是并行的,async 返回的是一个Deferred,需要调用其await()方法获取结果

  • withContext获取耗时任务结果

    btn.setOnClickListener {
        CoroutineScope(Dispatchers.Main).launch {
            val time1 = System.currentTimeMillis()
     
            val task1 = withContext(Dispatchers.IO) {
                delay(2000)
                Log.e("TAG", "1.执行task1.... [当前线程为:${Thread.currentThread().name}]")
                "one"  //返回结果赋值给task1
            }
                    
            val task2 = withContext(Dispatchers.IO) {
                delay(1000)
                Log.e("TAG", "2.执行task2.... [当前线程为:${Thread.currentThread().name}]")
                "two"  //返回结果赋值给task2
            }
     
            Log.e("TAG", "task1 = $task1  , task2 = $task2 , 耗时 ${System.currentTimeMillis()-time1} ms  [当前线程为:${Thread.currentThread().name}]")
        }
    }
    //运行结果
    1.执行task1.... [当前线程为:DefaultDispatcher-worker-1]
    2.执行task2.... [当前线程为:DefaultDispatcher-worker-1]
    task1 = one  , task2 = two , 耗时 3009 ms  [当前线程为:main]
    

    可见,多个withConext是串行执行,如上代码执行顺序为先执行task1再执行task2。这是因为withConext是个 suspend 函数,当运行到 withConext 时所在的协程就会挂起,直到withConext执行完成后再执行下面的方法。所以withConext可以用在一个请求结果依赖另一个请求结果的这种情况。

挂起函数
  • 挂起函数,挂起所在协程

    在一个协程内,挂起函数会保证挂起点后面的代码只能在挂起函数执行完后才能执行(async异步除外),所以挂起函数保证了协程内的执行顺序。但是这里并没有阻塞线程。

    代码实例

    fun main(){
        //创建一个协程
        GlobalScope.launch(Dispatcher.Unconfined) {
           fun1() //协程内调用挂起函数,该函数会挂起所在的协程,直到挂起函数执行完毕后才会继续执行后续fun2
           fun2()
        }
    }
    
    //挂起函数1
    suspend fun fun1(){
      delay(2000L)
      println("fun1")
    }
    
    //挂起函数2
    suspend fun fun2(){
      delay(1000)
      pintln("fun2")
    }
    
  • 挂起函数不会阻塞线程

    挂起函数挂起协程,并不会阻塞协程所在的线程,例如协程的delay()挂起函数会暂停协程一定时间,并不会阻塞协程所在线程,但是Thread.sleep()函数会阻塞线程。

    fun main(args: Array<String>) {
        // 创建一个单线程的协程调度器,下面两个协程都运行在这同一线程上
        val coroutineDispatcher = newSingleThreadContext("ctx")
        // 启动协程 1
        GlobalScope.launch(coroutineDispatcher) {
            println("the first coroutine")
            delay(200)
            println("the first coroutine")
        }
        // 启动协程 2
        GlobalScope.launch(coroutineDispatcher) {
            println("the second coroutine")
            delay(100)
            println("the second coroutine")
        }
        // 保证 main 线程存活,确保上面两个协程运行完成
        Thread.sleep(500)
    }
    //运行结果
    the first coroutine
    the second coroutine
    the second coroutine
    the first coroutine
    

    从上面结果可以看出,同一个线程内创建两个协程,当协程 1 暂停 200 ms 时,线程并没有阻塞,而是执行协程 2 的代码,然后在 200 ms 时间到后,继续执行协程 1 的逻辑。所以挂起函数并不会阻塞线程,这样可以节省线程资源,协程挂起时,线程可以继续执行其他逻辑。

Kotlin中获取Class类型对象

在Java中我们获取Class类型对象常用两种方法:对象获取和类获取

class Person{}

//对象方式获取Class类型对象
Person person = new Person();
Class class1 = person.getClass();

//类方式获取Class类型对象
Class class2 = Person.class

在kotlin中我们获取Class类型对象方式有

class Person{}

//对象方式获取Class类型对象
val person = Person()
val class1 = person.java.class    //javaclass
val class2 = person::class.java.  //javaclass

//类获取
val class3 = Person::class        //kClass

Android提供了Kotlin协程使用方式

在Activity/Fragment通过继承 CoroutineScope by MainScope()来在Activity/Fragment中使用协程,并在onDestroy中调用协程的cancel()方法取消当前当前协程作用域内所有子协程操作。

在搭建架构时我们可以在BaseActivity中使用如下:

//Activity基类继承CoroutineScope的代理类MainScope(),其自身和子类的内部可使用协程
open abstract class BaseActivity<T : IPresenter> : AppCompatActivity(), CoroutineScope by MainScope() {

    lateinit var mPresenter: T
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(setLayout())
        initView()
    }
    abstract fun setLayout(): Int
    abstract fun initView()

    override fun onDestroy() {
        super.onDestroy()
        //取消当前Activity协程作用域内的所有子协程
        cancel()
        mPresenter.detachView()
    }
}

在其子类Activity中可直接使用协程

class LoginActivity:BaseActivity<ILoginContract.AbsLoginPresenter>(),ILoginContractor.ILoginView,{

override fun setLayout(): Int {
        return R.layout.activity_login
    }

override fun initView() {
        ...
        //通过launch创建一个协程
        launch{
          //todo something...
        }
    }
}

当LoginActivity销毁的时候,回调用父类的onDestory()方法,取消LoginActiviy中作用域内的左右协程操作。

当然,我们通常不在View中直接操作Mode,而是通过Presenter来操作Mode,那么我们可以将View层的协程传递给Presenter中使用,使Presenter中的协程也受到View生命周期的约束

//LoginPresenter实现类(将父类的协程代理类scope传入,并让其继承)
class LoginPresenterImpl(mView: ILoginContractor.ILoginView, scope: CoroutineScope) :
    ILoginContractor.AbsLoginPresenter(mView), CoroutineScope by scope {
    override fun login() {
        //启动协程
        launch {
            mView!!.showDialog()
            try {
                //耗时操作切换协程运行至IO线程中
                var result = withContext(Dispatchers.IO) {              				    				                                 NetWorkUtil.creatorApiService(ITestApiServer::class.java, false, null).testMehod2()
                }
                if (result!!.code == 200) {
                    mView!!.success(result.data)
                } else {
                    mView!!.failed(result.message)
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
            mView!!.closeDialog()
        }
    }    
}

其他的类:

ILoginContractor:用于统一管理V、M

interface ILoginContractor {
    interface ILoginView : BaseView {
        open fun showDialog()
        fun closeDialog()
        fun success(value: UserInfo2)
        fun failed(errorMessage: String)
    }

    abstract class AbsLoginPresenter(mView: ILoginView) : BasePresenter<ILoginView>(mView) {
        abstract fun login()
    }
}

BasePresenter

/**
 *BasePrenser管理当前Presenter内的View实例
 */
open class BasePresenter<T:BaseView>(mView: T) : IPresenter {
  
    private var mMvpView: WeakReference<T>? = WeakReference(mView)

    var mView = let {
        try {
            mMvpView!!.get()
        } catch (e: Exception) {
            throw Exception(
                "Please call Presenter.attachView(MvpView) before\" +\n" +
                        "                    \" requesting data to the Presenter"
            )
        }
    }

    override fun detachView() {
        mMvpView?.clear()
        mMvpView = null
    }
}
Kotlin协程详解——协程基础 你通常会看到 runBlocking 在应用程序的最顶层被这样使用,而在真正的代码中却很少见到它的身影,因为线程是宝贵的资源,阻塞它们是低效的,而且通常是不被期望的。协程遵循结构化并发的原则,这意味着新的协程只能在特定的 CoroutineScope 内启动,该作用域限定了协程的生命周期。这些情况导致的问题是代码间的嵌套层级太深,导致逻辑嵌套复杂,后续的维护成本也要提高,这不是我们所要看到的。协程可以被视为轻量级的线程,但它们之间存在一些重要的区别,这使得它们在实际使用中与线程有很大的不同。 阅读详情

相关推荐

简易电梯控制系统设计(两层楼)(

(电子科技大学 数字系统实验二 结课验收项目 ) 题目要求:

m0_48801027的博客 1万+

kotlin 协程withContext切换线程

withContext 可以将当前线程从主线程切换到io线程。然后执行完毕再切换回来到ui线程执行操作

丿灬安之若死 3356

论文精读-ReTransformer ReRAM-based Processing-in-MemoryArchitecture for Transformer Acceleration

Transformer已经成为一种流行的深度神经网络(DNN)模型,用于神经语言处理(NLP)应用,并在神经机器翻译、实体识别等方面表现出优异的性能。然而,其在自回归解码器中的规模化点积注意机制在推理过程中带来了性能瓶颈。Transformer也是计算和内存密集型的,需要硬件加速解决方案。

a284365的博客 1922

【第26期】Coroutines(协程)我是这样理解的!

01什么是Coroutines(协程)协程是很久之前就提出的一个概念,目前支持协程的语言包括 lua、C#、go等。也包括Android官方开发语言Kotlin。当然网上对此也有很多的争...

华章IT官方博客 1070

硬件压力测试工具使用方法

B, --bind host# 绑定到主机的多个地址中的一个。当客户端到服务器端的测试结束时,服务器端通过-l选项指定的端口(或默认为客户端连接到服务器端的端口),反向连接至客户端。-c, --client host#如果Iperf运行在服务器模式,并且用-c参数指定一个主机,那么Iperf将只接受指定主机的连接。-C, --compatibility# 与低版本的Iperf使用时,可以使用兼容模式。-b, --bandwidth #[KM] UDP模式使用的带宽,单位bits/sec。

weixin_53303772的博客 8215

Kotlin协程使用,协程使用注意事项,协程中的await方法使用|不使用suspend使用协程

参见 码云 协程使用方法一 (Dispatchers调度器模式) 指定不同线程。同线程会挂起并阻塞(挂起是不影响主线程执行,阻塞是同样的IO线程会阻塞) withContext(Dispatchers.IO) { // ???? 切换到 IO 线程,并在执行完成后切回 UI 线程 println("1") delay(1000) } 协程使用方法二 (GlobalScope.async) 使用GlobalScope.async返回Deferred类型对象。 继承自 Job 接口,额外提

了迹奇有没 1597

【ArcGIS Engine二次开发】入门基础(2):ArcGIS开发方式(VBA、DLL、Add-in、Engine)对比

文章目录2.1 使用VBA进行桌面软件开发2.1.1 VBA开发方式2.1.1 VBA代码的安全性2.2 使用DLL进行桌面软件开发2.2.1 DLL开发方式2.2.2 DLL功能的应用部署2.3 使用Add-in进行桌面软件开发2.4 使用ArcGIS Engine构建独立应用程序 对于桌面GIS应用来说,ArcGIS平台不仅提供了用于开发独立运行的GIS应用软件的二次开发工包ArcGIS Engine,还提供了对ArcGIS Deskop桌面软件进行扩展的开发的话,在开发个具体的GIS 行业应用软件时

智慧空间实验室 6509

Kotlin协程-Coroutines的基本使用

Kotlin协程的基本使用Kotlin协程系列:协程的基本使用(本文)协程的上下文理解协程的作用域管理协程的常见进阶使用其实网上已经有很多Kotlin协程的教程了,这里我出一期是为了记录自己的总结。也是自己的理解与输出。由于协程的概念是一个比较大的东西,这里我分解成几个不同的模块来讲解,本文是系列的第一期,本系列讲的是协程各模块的偏实战使用,并不会过多的涉及到原理与源...

Android技术之家 764

如何通过 GitHub Actions 使用 SSH 自动化部署到阿里云 ECS 实例

通过 GitHub Actions 和 SSH 自动化部署,可以显著提高你的开发效率和部署的可靠性。本文介绍了如何生成 SSH 密钥、将公钥添加到阿里云 ECS 实例、将私钥添加到 GitHub Secrets,以及编写 GitHub Actions 工作流来实现自动化部署。希望这篇文章对你有所帮助!

吃面不喝汤的博客 1511

WorkBuddy 技能指南:基础办公与文档处理

WorkBuddy 不仅仅是一个对话工具——它通过技能(Skills)系统,将通用 AI 助手转变为具备专业领域能力的智能助手。每个技能都是一套经过精心设计的"能力包",包含专业知识、工作流程和专用工具。本文聚焦于文档处理类技能——这是日常工作中使用频率最高、最实用的能力集合。

u011186532的专栏 703

【kotlin 协程】万字协程 一篇完成kotlin 协程进阶

在 Android 平台上,协程主要用来解决两个问题:从本质上来说,协程就是一个轻量级的线程。在使用协程之前,我们需要先引入Coroutine 的包 创建协程的方式有很多种,这里不延伸协程的高级用法(热数据通道Channel、冷数据流Flow.…),创建协程这里介绍常用的三种方式: runBlocking 启动一个协程会阻塞调用它的线程,只到里面的代码执行结束,返回值是泛型T。 launch启动一个协程不会阻塞调用线程,必须要在协程作用域(CoroutineScope)中才能调用,返回值是一个Job asy

半身风雪 5676

错误代码105(ERR_NAME_NOT_RESOLVED)详解与解决方案

错误代码105表示"",当您的浏览器无法将网站域名转换为IP地址时就会出现此错误。这是最常见的网络连接问题之一,通常与DNS(域名系统)有关。

欢乐打代码! 1万+

Kotlin 协程 看这一篇就够了

前言 Kotlin协程是什么,如何使用?如何结合Retrofit使用?Kotlin 的优势在哪里?相信看完这一篇你一定有所收获! 协程基本使用 若使用协程,首先我们得引入协程相关的开发包 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7-mpp-dev-11' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7-mpp-de

黄林晴 1万+

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

【Kotlin】协程

相较于 C# 中的协程(详见 →),Kotlin 中协程更灵活,难度更大。协程是一种并发设计模式,用于简化异步编程,它允许以顺序化的方式表达异步操作,避免回调地狱等问题。使用协程,可以将异步操作的代码像同步代码一样写,而无需显式地管理线程。在 Kotlin 中,协程由 kotlinx.coroutines 库提供支持。它使用 suspend 修饰符来标记挂起函数(即可暂停执行并稍后恢复执行的函数),这使得编写异步代码更加直观和简单。协程和线程具有以下异同点。

little_fat_sheep 2380

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Kotlin协程的使用

目录 引入kotlin协程 kotlin协程的三种启动方式 初识协程: 第一种启动方式(runBlocking:T) 第二种启动方式(launch:Job) 第三种启动方式(async/await:Deferred) 实现你的第一个Coroutine程序 浅析Kotlin协程 ——协程是什么?https://blog.csdn.net/qq_17798399/article/det...

青冘的技术学习博客 1万+

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

一文看透 Kotlin 协程本质

前言 公司开启新项目了,想着准备亮一手 Kotlin 协程应用到项目中去,之前有对 Kotlin 协程的知识进行一定量的学习,以为自己理解协程了,结果……实在拿不出手! 为了更好的加深记忆和理解,更全面系统深入地学习 Kotlin 协程的知识,协程将分为三部分来讲解,本文是第一篇: Kotlin 协程实战进阶(基础篇) Kotlin 协程实战进阶(进阶篇) Kotlin 协程实战进阶(高级篇) 本文大纲 一、概述 协程的概念在1958年就开始出现(比线程还早), 目前很多语言开始原生支, Java

https://juejin.cn/user/1654096907477549/posts 7729

kotlin协程入门

可以简单地将它理解成一种轻量级的线程。我们之前所学习的线程是非常重量级的,它需要依靠操作系统的调度才能实现不同线程之间的切换。而使用协程却可以仅在编程语言的层面就能实现不同协程之间的切换,从而大大提升了并发编程的运行效率。为什么使用协程?简洁性:协程使得异步代码看起来像同步代码,减少了回调地狱的问题。轻量级:协程比线程更轻量,可以在同一线程中运行多个协程。易于管理:协程可以通过结构化并发来管理生命周期,避免内存泄漏和资源浪费。

weixin_63357306的博客 1304

Kotlin协程机制

协程是一种轻量级线程,它可以挂起(暂停)和恢复,从而使异步代码看起来像同步代码,极大地提升代码的可读性和结构清晰度。📌 类比:传统的线程是“重型卡车”,协程是“轻便摩托车”——更轻、更灵活。优势说明✅ 语法简洁让异步代码看起来像同步✅ 非阻塞不阻塞线程,提高性能✅ 生命周期安全可与 Android 生命周期绑定✅ 性能优越比线程更轻量、资源占用少✅ 原生支持不依赖第三方库,语言级支持xie che。

王景程的博客 1710

Kotlin 之协程

协程的基本使用及浅析

禽兽先生不禽兽 1627
上一篇: Kotlin基础
下一篇: Kotlin高阶函数及Lambda表达式实现接口回调
LoadTown
博客等级 码龄14年 109粉丝 80原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值