Kotlin协程
- 前言
- 协程基本概念和使用
- 协程使用的基本函数
- 示例代码
抓住今天,尽可能少的信赖明天。 喝汤能补 (* ^ ▽ ^ *)
前言
该文章作为学习交流,如有错误欢迎各位大佬指正 (* ^ ▽ ^ *)
- 自身技能
(1)已具备计算机的基本知识 - 本文简介
主要讲解:协程的概率,使用方式,协程所用到的api。
协程基本概念和使用
协程,和线程类似,可以理解成一种轻量级的线程,协程可以仅在编程语言的层面就能实现不同协程之间的切换,这样就提升了并发编程的运行效率。线程是重量级的,需要依靠操作系统的调度才能实现不同线程之间的切换。
- 协程允许我们在单个线程模式下模拟多线程编程的效果,代码执行时的挂起与恢复完全是由编程语言来控制的,和操作系统无关。
下面代码。没有开启线程情况下,理论上的结果是a0a1a2d0d1d2 。使用协程之后,在执行eatA()时,随时可能被挂起转而去执行eatD()方法,执行eatD()方法时,随时可能被挂起转而去执行eatA()方法,这样结果就不确定了。
fun main() {
fun eatA(){
print("a0")
print("a1")
print("a2")
}
fun eatD(){
print("d0")
print("d1")
print("d2")
}
}
协程使用的基本函数
先添加协程依赖库
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
- GlobalScope.launch 函数每次创建都是顶层协程,这种协程的生命周期和应用程序(简称程序)生命周期绑定。即,GlobalScope.launch 函数创建的都是顶层协程,当应用程序结束,协程也就结束了。
- Thread.sleep() 方法会 阻塞当前的线程,这样运行在该线程下的所有协程都会被阻塞。
- runBlocking函数 ,会创建一个作用域,但可以保证在协程作用域内所有代码和子协程没有全部执行完之前,一直阻塞当前线程。
- runBlocking和GlobalScope.launch通常用于测试环境,正式环境中使用容易产生一些性能上的问题。
- aunch函数 ,可以用来创建多个协程。launch函数必须在协程作用域中才能调用,会在当前协程的作用域下创建子协程。
- 子协程 ,特点是如果外层作用域的协程结束了,该作用域下的所有子协程也会一同结束。
- delay()函数,可以让当前协程延迟指定时间后再执行,是非阻塞式的挂起函数,只会挂起当前协程,不影响其他协程的运行。作用范围:只能在协程的作用域或其他挂起函数中调用,自身也是挂起函数。
- 挂起函数,在函数的关键字fun前加上suspend关键字,那么该函数就是挂起函数。主要作用是:能在协程作用域中被调用,因为协程作用域中无法调用其他普通函数。 挂起函数之间可以互相调用,且无法提供协程作用域。
- coroutineScope挂起函数,它会继承外部的协程作用域并创建一个子作用域,这样就可以给任意挂起函数提供协程作用域了。另外,可以保证在协程作用域内所有代码和子协程没有全部执行完之前,一直阻塞当前线程,只要执行完之后,才运行后面的代码。
- coroutineScope函数只会阻塞当前协程,即不影响其他协程,也不影响任何线程;runBlocking函数由于会阻塞当前线程,所以可能会造成性能上的问题,coroutineScope函数则不会造成。
- GlobalScope.launch与runBlocking函数是可以任意地方调用的,coroutineScope函数可以在协程作用域或者挂起函数中调用,而launch函数只能在协程作用域中调用
- cancel()函数 ,是Job中的函数,用于取消协程。GlobalScope.launch函数和launch函数,都会返回一个Job对象,通过Job对象调用cancel()即可取消协程。
- CoroutineScope()函数 ,CoroutineScope()函数接收一个Job对象,并返回一个CoroutineScope对象,通过CoroutineScope对象调用launch函数创建一个协程了,所有调用CoroutineScope的launch函数所创建的协程,都关联在Job对象的作用域下,这就可以只调用一次cancel()函数,就将同一作用域内的所有协程全部取消。避免每次创建顶层协程,都需要调用cancel()函数进行取消,不方便后期维护。
- async函数,必须在协程作用域当中才能调用,它会创建一个新的子协程并返回一个Deferred对象,使用Deferred对象的await()方法就可以得到async函数代码块的执行结果。 调用await()函数时,如果代码没执行完,就会阻塞当前协程,直到获取async函数的执行结果。
- async函数可以获取到创建的协程的执行结果,而launc函数不能获取创建的协程的执行的结果,因为它的返回值永远是个Job对象。
- withContext函数,必须指定一个线程参数。调用后,立即执行代码块中代码,同时将当前协程阻塞住。当代码块全部执行完,就将最后一行的执行结果作为withContext()函数的返回值返回。
- 线程参数:Dispatchers.Default表示会使用一种默认低并发的线程策略,用于计算密集型任务时,开启过高的并发可能会影响任务运行效率的场景。Dispatchers.IO表示会使用一种较高并发的线程策略,用于大多数时间是在阻塞和等待中,如网络请求。Dispatchers.Main表示不会开启子线程,在android主线程中执行代码,只有在Android项目中使用,纯Kotlin程序使用该参数会出现错误。 我们在主线程里面的协程中处理耗时操作,也可能引发程序错误。
- suspendCoroutine函数,必须在协程作用域或挂起函数中才能调用,它接收一个Lambda表达式参数,主要作用是将当前协程立即挂起,然后在一个普通的线程中执行Lambda表达式中的代码。Lambda表达式的参数列表上会传入一个Continuation参数,调用它的resume方法或者resumeWithException()可以让协程恢复执行。
示例代码
开启一个协程例子,如使用GlobalScope.launch函数。这里GlobalScope.launch函数可以创建一个协程作用域,传给launch函数的代码块(Lambda表达式)就在协程中运行的。
不加Threa.sleep(1000)就会发现,没有日志打印,因为当应用程序运行完时,协程也会结束,导致println(“World!”)未来得及执行。加了之后,进行了延迟,也就让该打印信息执行了。
fun main() {
GlobalScope.launch { // 协程的生命周期和应用程序生命周期绑定
println("World!")
}
Thread.sleep(1000L) // 阻塞主线程 1 秒钟
}
如果程序1s内未执行完,那么就会被强制中断。如下面程序,World!就未打印。
fun main() {
GlobalScope.launch {
println("start")
delay(1500)
println("World!")
}
Thread.sleep(1000L) // 阻塞主线程 1 秒钟
}
runBlocking函数使用
fun main() {
runBlocking {
println("start")
delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
println("World!")
}
}
//结果
//start
//World!
launch函数使用
runBlocking {
launch {
println("A")
delay(1000)
println("A end")
}
launch {
println("B")
delay(1000)
println("B end")
}
}
// A
// B
// A end
// B end
coroutineScope,它会继承外部的协程作用域并创建一个子作用域,这样就可以给任意挂起函数提供协程作用域了。另外,可以保证在协程作用域内所有代码和子协程没有全部执行完之前,一直阻塞当前协程,只要执行完之后,才运行后面的代码。
suspend fun eatThing() = coroutineScope {
launch {
println("Hello")
delay(1000)
}
}
Job与cancel()函数使用
val job = GlobalScope.launch {
// 处理具体的逻辑
}
job.cancel()
每次创建顶层协程,都需要在取消的时候调用协程的cancel()方法,这样就不方便后期维护。可以创建Job对象,传递给CoroutineScope()函数,该函数返回一个CoroutineScope对象,通过CoroutineScope对象就可以调用launch函数创建一个协程了。
val job = Job()
val scope = CoroutineScope(job)
scope.launch {
// 处理具体的逻辑
}
job.cancel()
// ret AaBb
// cost 2028 ms
调用await()函数时,如果代码没执行完,就会阻塞当前协程,直到获取async函数的执行结果。
// 两个协程串行进行的
runBlocking {
val start = System.currentTimeMillis()
val ret = async {
delay(1000)
"A"+"a"
}.await()
val ret2 = async {
delay(1000)
"B"+"b"
}.await()
println(" ret ${ret + ret2} ")
val end = System.currentTimeMillis()
println(" cost ${end - start} ms ")
}
// 优化,将await()函数在协程执行完后,统一调用,这样两个协程就是并行进行的了
runBlocking {
val start = System.currentTimeMillis()
val ret = async {
delay(1000)
"A"+"a"
}
val ret2 = async {
delay(1000)
"B"+"b"
}
println(" ret ${ret.await() + ret2.await()} ")
val end = System.currentTimeMillis()
println(" cost ${end - start} ms ")
}
suspendCoroutine函数优化网络请求,下面代码只是示例,无法完整的运行成功
suspend fun request(address: String): String{
return suspendCoroutine { continuation ->
sendHttp(address,object : HttpCallbackListener){
override fun onFinish(response: String){
continuation.resume(response)
}
override fun onError(e: Exception){
continuation.resumeWithException(e)
}
}
}
}
// 使用
suspend fun getBaiduResponse(){
try {
var response = request("https://www.baidu.com")
}catch (e: Exception){
}
}