文章目录
之前的线程非常重量级,需要依靠操作系统的调度才能实现不同线程之间的切换。
协程:一种轻量级的线程,仅在编程语言的层面就能实现不同协程之间的切换,从而大大提升了并发编程的运行效率,允许在单线程模式下模拟多线程编程的效果,代码执行时的挂起与恢复完全由编程语言来控制。
1.协程的基本用法
1.1 先添加依赖库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1"
// 此依赖库是 Android 项目才会用到的,纯 Kotlin 程序其实用不到。
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
1.2 开启一个协程
fun main(){
GlobalScope.launch {
println("codes run in coroutine scope")
//日志无法打印,因为代码块中的代码还没来得及运行,应用程序就结束了
}
}
GlobalScope.launch函数
可以创建一个协程的作用域,函数的代码块就是在协程中运行的。
GlobalScope.launch函数每次创建的都是一个顶层协程,这种协程当应用程序结束时也会跟着一起结束
fun main(){
GlobalScope.launch {
println("codes run in coroutine scope")
delay(1500)
println("code run in coroutine scope finished")
//不会被打印出来,没有来得及
}
Thread.sleep(1000)
}
delay()函数
让当前协程延迟指定时间后再运行;非阻塞式的挂起函数,只会挂起当前协程,不影响其他协程的运行;只能在协程的作用域或其他挂起函数中调用
Thread.sleep()函数
会阻塞当前线程,运行在该线程下的所有协程都会被阻塞
runBlocking {
println("codes run in coroutine scope")
delay(1500)
println("code run in coroutine scope finished")
}
runBlocking函数
会创建一个协程的作用域,保证在协程作用域内的所有代码和子协程没有全部执行完之前一直阻塞当前线程(一般在测试环境下使用)
1.3 创建多个协程
使用launch函数
fun main(){
runBlocking {
launch {
println("launch1")
delay(1000)
println("launch1 finished")
}
launch {
println("launch2")
delay(1000)
println("launch1 finished")
}
}
}
//结果:
launch1
launch2
launch1 finished
launch1 finished
//像多线程那样并发运行,实际这两个子协程运行在同一个线程中
launch函数
- 必须在协程的作用域中调用
- 会在当前协程的的作用域下创建子协程
子协程:如果外层作用域的协程结束了,该作用域下的所有子协程也会一同结束
只是由编程语言决定如何在多个协程之间进行调度,不需要操作系统参与,使协程的并发效率很高
suspend关键字
- 将任意函数声明成挂起函数,挂起函数之间可以相互调用
- 只能将一个函数声明成挂起函数,无法提供协程作用域
(下面的代码printDot()里面无法调用launch函数,launch函数要求必须在协程作用域中才能调用)
suspend fun printDot(){
println(".")
delay(1000)
}
coroutineScope函数
- 挂起函数,可以在任何其他挂起函数中调用
- 会继承外部的协程的作用域并创建一个子协程
- 可以保证其作用域内的所有代码和子协程在全部执行完之前,外部的协程会一直被挂起
- 只会阻塞当前协程,不影响其他协程和线程
suspend fun printDot() = coroutineScope{
launch {
println(".")
delay(1000)
}
}
2. 更多的作用域构建器
GlobalScope.launch:任意地方都可调用
runBlocking:任意地方调用
coroutineScope:协程作用域或挂起函数中调用
lanuch:在协程作用域中调用
GlobalScope.launch函数和lanuch函数都会返回一个Job对象,调用Job对象的cancel()方法
val job = GlobalScope.launch {
//具体逻辑
}
job.cancel()
实际项目中比较常用的写法:
val job = Job()
val scope = CoroutineScope(job)
//CoroutineScope()是个函数
//有了CoroutineScope对象之后就可以随时调用它的launch函数创建一个协程
scope.launch {
//处理具体逻辑
}
job.cancel()
async函数
- 必须在协程作用域中才能调用
- 会创建一个新的子协程并返回一个Deferred对象,想要获取async函数代码块的执行结果,调用Deferred对象的await()方法
- await()方法会将当前协程阻塞住,直到可以获得async函数的执行结果
runBlocking {
val result = async {
5+5
}.await()
println(result)
}
withContext()函数
- 是一个挂起函数
- 调用之后会立即执行代码块中的代码,同时将外部协程挂起, 代码块中的代码全部执行完后会将最后一行的执行结果作为withContext()函数的返回值返回。
- 强制要求指定一个线程参数
线程参数
可以通过线程参数给协程指定一个具体的运行线程
3.使用协程简化回调的写法
suspendCoroutine函数
- 将传统回调机制的写法大幅简化
- 必须在协程作用域或挂起函数中调用
- 接收一个Lambda表达式参数
- 主要作用是将当前协程立即挂起,然后在一个普通的线程中执行Lambda表达式中的代码
- Lanmbda表达式的参数列表上会传入一个Continuation参数,调用它的resume()方法或resumeWithException()可让协程恢复执行
例子:
//1.首先定义一个request()函数
suspend fun request(address:String):String{
//挂起函数,接受一个address参数
//调用suspendCoroutine函数,当前协程会被立刻挂起,Lambda表达式中的代码会在普通线程中执行
return suspendCoroutine { continuation ->
HttpUtil.sendHttpRequest(address,object : HttpCallbackListener{
//HttpUtil.sendHttpRequest()方法发起网络请求,通过传统回调方式监听请求结果
override fun onFinish(response:String){
continuation.resume(response)
//请求成功,Continuation的ewsume()方法恢复被挂起的协程
//并传入服务器响应的数据,该值会成为suspendCoroutine函数的返回值
}
override fun onError(e:Exception){
continuation.resumeWithException(e)
//请求失败,调用continuation.resumeWithException()恢复被挂起的协程
//并传入具体的异常原因
}
})
}
}
//2.使用
suspend fun getBaiduResponse(){
try {
val response = request("https://www.baidu.com/")
//对服务器响应的数据进行处理
}catch (e:Exception){
//对异常情况进行处理
}
}
使用Retrofit发起网络请求
val appService = ServiceCreator.create<AppService>()
appService.getAppData().enqueue(object : Callback<List<App>>{
override fun onResponse(call:Call<List<App>>,response:Response<List<App>>){
//得到服务器返回的数据
}
override fun onFailure(call:Call<List<App>>,t:Throwable){
//对异常情况进行处理
}
})
使用suspendCoroutine函数进行简化
//不同的Service接口返回的数据类型不同,使用泛型的方式,定义一个await()函数
//await()函数是一个挂起函数,声明了一个泛型T,将await函数定义成了Call<T>的扩展函数
//所有返回值是Call类型的Retrofit网络请求接口都可以直接调用await()函数
suspend fun <T> Call<T>.await():T{
//使用suspendCoroutine函数挂起当前协程
//现在拥有了Call对象的上下文,直接调用enqueue()方法让Retrofit发起网络请求
return suspendCoroutine { continuation ->
enqueue(object :Callback<T>{
override fun onResponse(call: Call<T>?, response: Response<T>?) {
val body = response.body()
if(body!=null) continuation.resume(body)
else continuation.resumeWithException(
RuntimeException("response body is nulll"))
}
override fun onFailure(call: Call<T>?, t: Throwable?) {
continuation.resumeWithException(t)
}
})
}
}
//使用
suspend fun getAppData(){
try{
val appList = ServiceCreator.create<AppService>().getAppData().await()
//得到服务器返回的数据
}catch(e:Exception){
//对异常情况进行处理66
}
}
4.Kotlin使用DSL构建专有的语法结构
通过DSL可以编写出一些看似脱离其原始语法结构结构的代码,从而构建出一种专有的语法结构
添加依赖库
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
4.1. 新建一个DSL.kt文件
class Dependency{
val libraries = ArrayList<String>()
//List集合保存所有的依赖库
fun implementation(lib:String){
//提供一个implementation()方法用于向List集合中添加依赖库
libraries.add(lib)
}
}
4.2. 定义一个dependencies高阶函数
fun dependencies(block:Dependency.() -> Unit):List<String>{
val dependency = Dependency()
dependency.block()
return dependency.libraries
}
4.3 可以在项目中使用如下语法结构:
dependencies {
implementation ("com.squareup.retrofit2:retrofit:2.6.1")
implementation ("com.squareup.retrofit2:converter-gson:2.6.1")
}
//dependencies函数接收一个函数类型参数,这里传入一个Lambda表达式
//此时Lambda表达式拥有Dependency类的上下文,可以直接调用Dependency类中的implementation()方法