简介
协程是一种并发设计模式,可以使用协程来简化异步代码
为什么需要协程
顺序执行的代码是同步的,即下面的工作要执行必须等待之前的工作执行完毕,每一行代码都会阻塞当前的线程,显然主线程阻塞会导致明显的卡顿 ,界面呈现速度缓慢或界面冻结,对触摸事件的响应速度很慢,所以我们需要将耗时的任务放到主线程之外运行
多线程执行代码,可以在不同的线程上执行顺序的代码,然后通过异步函数完成线程之间的切换工作,换言之异步回调即代码的多线程顺序执行
在Android平台上,协程有助于解决两个主要问题:
协程使用
首先在app/build.gradle中添加以下依赖库
implementation
'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1'
implementation
'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
@Test
fun testCoroutine() {
GlobalScope.launch {// 创建一个协程的作用域
delay(1500L) // 非阻塞式挂起函数,会挂起当前协程,并不影响其他协程的运行
println("world!")
}
println("Hello, ")
Thread.sleep(2000L) // 会阻塞当前线程,运行在改下成写的所有协程都会被阻塞,阻塞主线程保证JVM存活
}
结果:
Hello,
world!
当主线程的阻塞时间小于1500L时,只能打印出Hello,此时协程未来得及执行
为了解决上述协程未来得及执行的问题:引入runBlocking
fun testCoroutine() {
runBlocking {// 创建一个协程的作用域,此时该作用于中的协程全部执行完之前一直阻塞当前线程(会影响主线程)
delay(1500L) // 非阻塞式挂起函数,会挂起当前协程,并不影响其他协程的运行
println("world!")
}
println("Hello, ")
Thread.sleep(1000L) // 会阻塞当前线程,运行在改下成写的所有协程都会被阻塞,阻塞主线程保证JVM存活
}
// 结果:
world!
Hello,
1.使用launch函数启动携程
fun testCoroutine() {
runBlocking {
launch {
println("launch1")
delay(1000)
println("launch1 finish")
}
launch {
println("launch2")
delay(1000)
println("launch2 finish")
}
}
}
// 结果:
launch1
launch2
launch1 finish
launch2 finish
2. 使用async启动协程
//三次请求并发进行
coroutineScope {
val deferredOne = async { fetchDoc(1) }
val deferredTwo = async { fetchDoc(2) }
val deferredThree = async { fetchDoc(3) }
//所有结果全部返回后更新UI
updateUI(deferredOne.await(), deferredTwo.await(), deferredThree.await())
}
suspend fun fetchDoc(){...}
挂起
挂起的协程不会阻塞所在的线程但会阻塞当前挂起的协程,线程继续执行下面的代码,协程则等待直到线程回来执行协程中的代码
协程的挂起即协程中的代码离开协程所在的线程,协程恢复即协程中的代码重新进入协程所在的线程,协程通过挂起恢复机制实现线程的切换
suspend关键字
suspend意为挂起, 使用suspend声名的函数和上面的delay函数一样是挂起函数(挂起函数必须在协程或者其他挂起函数中被调用即挂起函数必须直接或者间接的在协程作用域中执行)
suspend函数并不能起到切换线程的作用,它的存在只是说明被它修饰的函数是一个耗时函数
suspend fun testSuspend(){
println("suspend ${Thread.currentThread().name}")
}
fun testCoroutine() {
GlobalScope.launch(Dispatchers.Main) {// 创建一个协程的作用域
println("Start ${Thread.currentThread().name}")
testSuspend()
println("End ${Thread.currentThread().name}")
}
}
// 结果:
Start main -> suspend main -> End main
使用suspend并不能切换线程,那么我们就要使用另一个作用域构造器withContext
withContext使用说明
调用withContext之后,会执行代码块中的代码同时会阻塞当前协程,当代码块执行完毕之后回京最后一行执行结果作为withContext的返回值返回
withContext会要求传入一个线程参数,因为Android中I/O操作一般在子线程中进行,如果开启主线程上的协程去做网络请求显然也是不可以的,所以必须要给协程指定一个线程去执行相应的操作
线程参数如下
Dispatchers.Main : 使用此调度程序可在Android主线程上运行协程,此调度程序只能用于与界面交互以及执行快速工作,这个值只能在Android项目中使用,纯Kotlin程序使用此类型会报错
Dispatchers.IO :此调度程序适合在主线程之外执行磁盘和网络I/O,是一种叫高并发的线程策略
Dispatchers.Defaul t:此调度程序适合在主线程之外执行占用大量的CPU资源的工作,是一种低并发的线程策略
使用withContext进行挂起函数中的线程切换
fun main() {
GlobalScope.launch(Dispatchers.Main) {// 创建一个协程的作用域
println("Start ${Thread.currentThread().name}")
testSuspend()
println("End ${Thread.currentThread().name}")
}
}
suspend fun testSuspend() {
withContext(Dispatchers.IO) {
println("suspend ${Thread.currentThread().name}")
}
}
// 结果
Hello main -> suspend DefaultDispatcher-worker-1 -> End main