乐于分享
好东西不私藏

保姆级学习开发安卓手机软件(二十六)--同步、异步编程

保姆级学习开发安卓手机软件(二十六)--同步、异步编程
1.打开Kotlin 园地
地址:https://play.kotlinlang.org/
2.同步时延
kotlin也有时延函数(delay):
import kotlinx.coroutines.*funmain() {    runBlocking {        println("Weather forecast")        delay(5000)        println("Sunny")    }}
这里解释下,如果单用delay(5000),是会报错,必须要加runBlocking {}。
那如果我们要在自己的函数里面用时延,则还需要添加关键字suspend
import kotlinx.coroutines.*funmain() {    runBlocking {        println("Weather forecast")        printSunny()    }}suspend funprintSunny(){    delay(5000)        println("Sunny")}
如果我们想知道所有代码具体执行时长:
import kotlin.system.*import kotlinx.coroutines.*funmain() {    var time=measureTimeMillis{        runBlocking {            println("Weather forecast")            printSunny()        }    }    println("Execution time: ${time / 1000.0} seconds")}suspend funprintSunny(){    delay(5000)        println("Sunny")}
3.异步
从上个步骤继续往下走(时间改短一点,不然这里运行会报错),如果我们有两个类似时延5秒的函数,时间就会比较久:
那并行就是用另一个函数launch{}
import kotlin.system.*import kotlinx.coroutines.*funmain() {    var time=measureTimeMillis{        runBlocking {            println("Weather forecast")            launch{            	printSunny()            }            launch{            	printSunny2()            }        }    }    println("Execution time: ${time / 1000.0} seconds")}suspend funprintSunny(){    delay(3000)    println("Sunny")}suspend funprintSunny2(){    delay(3000)    println("Sunny2")}
这就是简单的异步(并发运行),然后我们新增一个打印,看看结果:
这里就能看出来,代码写在最后,但是打印是在前面的,异步原理,其实也就是新增了几个线程,处理耗时长的内容,但是不耽误原线程后续执行内容。
这块可能有一个困惑点吧,如果把打印位置放在runBlocking {}后面呢,效果是如何?大家可以尝试下,这次,Weather forecast2就会出现在最后了。

因为runBlocking的核心特性:它会阻塞外部线程,直到自己内部所有子协程全部完成才放行

  • 单 launch 场景:内部只有一个 3s 协程 → runBlocking 卡 3s → 总耗时 3s;
  • 双 launch 场景:两个 3s 协程并发跑完 → runBlocking 只卡 3s,不是叠加 6s。
  • runBlocking的作用:

    协程容器:提供作用域,里面多个launch天然并发;
  • 阻塞屏障:会死死等内部所有子协程全部结束,才退出代码块、释放外面的主线程。
  • 4.异步返回
  • 上面我们只是使用了异步,但是不能控制它在执行完后再去执行某一项操作,这时候使用 launch() 的方法是不够的。在这种情况下,async() 就可以派上用场了。
  • async() 函数会返回一个类型为 Deferred 的对象,可以使用 await() 访问 Deferred 对象上的结果。
  • 先改造两个时延函数,令其有返回值:
    suspend funprintSunny():String{    delay(3000)    return "Sunny"}suspend funprintSunny2():String{    delay(3000)    return "Sunny2"}
    在主程序中进行延时函数返回值的使用:
    funmain() {    var time=measureTimeMillis{        runBlocking {            println("Weather forecast")            val ps1:Deferred<String> = async{            	printSunny()            }            val ps2:Deferred<String> = async{            	printSunny2()            }            println("${ps1.await()}${ps2.await()}")            println("Weather forecast2")        }    }    println("Execution time: ${time / 1000.0} seconds")}
    再去看结果:
Weather forecast 
Sunny Sunny2 
Weather forecast2 
Execution time: 3.127 seconds
就可以看出,这个时延函数返回的值在时延后被使用,进行了打印操作。
这个我们写代码的一般说,回调函数。
最后我们再加入coroutineScope(局部作用域)使用:
import kotlin.system.*import kotlinx.coroutines.*funmain() {    var time=measureTimeMillis{        runBlocking {            println("Weather forecast")            println(printSunnySum())            println("Weather forecast2")        }    }    println("Execution time: ${time / 1000.0} seconds")}suspend funprintSunnySum()=coroutineScope{    val ps1= async{            	printSunny()            }    val ps2 = async{                printSunny2()            }    "${ps1.await()}${ps2.await()}"}suspend funprintSunny():String{    delay(3000)    return "Sunny"}suspend funprintSunny2():String{    delay(3000)    return "Sunny2"}
原来的代码和这次代码的区别是从async 直接写在 runBlocking 里,变成async 封装进coroutineScope挂起函数。
我们推荐后者的做法,原因有三点:
  • 可复用
    并发逻辑打包成一个suspend函数,别的地方想同时跑这两个任务,直接调用printSunnySum()即可;第一段代码的 async 逻辑写死在 runBlocking 里,不能复用。
  • 异常隔离
    内部两个任务报错只会影响printSunnySum自身,外层代码不受牵连;第一段某个 async 出错会直接整段 runBlocking 崩溃。
  • 结构化并发规范
    一组绑定在一起的并行任务收进独立作用域,取消、生命周期统一管理,不会出现协程泄漏,是项目标准写法。
    • 5.异步取消
    • 取消很简单,就是cancel()函数:
      suspend funprintSunnySum()=coroutineScope{    val ps1= async{            	printSunny()            }    val ps2 = async{                printSunny2()            }    delay(1000)    ps1.cancel()//这里取消了第一个时延函数    ${ps2.await()}"}
      结果: