参考资料
- 协程的所在核心的Github:
- 协程的官方文档
Kotlin Coroutines(协程) 完全解析(二),深入理解协程的挂起、恢复与调度
如何正确的在Android上使用协程?_秉心说-CSDN博客
环境配置
截至2019/5/20 22:46 Gradle配置如下:
Java:(IDEA)
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
}
Andriod:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
基本应用场景
协程最基本的用法
val job = GlobalScope.launch{
}
//可以在需要时取消
job.cancel();
注:启动一个新的协程,而不会阻塞当前线程,返回一个协程Job,Job可用来控制对应协程序,比如cancel
如何让协程运行在主线程上
- 方案一:
GlobalScope.launch(Dispatchers.Main){
println("执行协程")
}
- 方案二
MainScope().launch {
println("执行协程")
}
协程的线程池调度策略(协程的本质是线程池的调度)
如上例,我们可以用Dispatchers.Main使协程运行到主线程,不同的Dispatchers定义了不同的协程调度模式
GlobalScope.launch(Dispatchers.Main){
println("在主线程执行协程")
}
策略 | 描述 |
---|---|
Dispatchers.Main | 运行在主线程/UI线程上 |
Dispatchers.Unconfined | 运行在当前所在线程 |
Dispatchers.Default | 默认值的CoroutineDispatcher,JVM共享线程池,保证最大程度的并行性,线程数量等于CPU的核心数,最少为2 |
Dispatchers.IO | IO线程池,它默认为64个线程的极限或核的数量的最大值 |
协程中的堵塞:sleep及dalay
- 一般情况下,非常不建议在协程中用 Thread.sleep()来实现堵塞(会直接堵塞线程),建议用 delay()来实现协程的堵塞(可以被取消且仅挂起协程而不堵塞线程)
- 默认情况下,协程中的Thread.sleep()并不影响所在线程后续代码的执行,这种不影响当前所在线程的状态,称之为挂起,如下:
fun testBlock(){
println("${LocalDateTime.now()}:开始测试")
GlobalScope.launch {
println("${LocalDateTime.now()}:协程开始运行")
Thread.sleep(2000)
println("${LocalDateTime.now()}:协程结束运行")
}
Thread.sleep(500)//确保协程已经被启动
println("${LocalDateTime.now()}:结束测试")
}
System.out: 2022-10-19T03:04:03.509:开始测试
System.out: 2022-10-19T03:04:03.532:协程开始运行
System.out: 2022-10-19T03:04:04.031:结束测试
System.out: 2022-10-19T03:04:05.533:协程结束运行
注:这很好理解,因为协程的本质是线程池,把GlobalScope.launch看成Thread{ },就明白了。换一种说法,如果我们让协程运行的线程是当前所在线程,那么就会导致整体堵塞,无法挂起了,如下:
fun testBlock(){
println("${LocalDateTime.now()}:开始测试")
//让协程运行在当前所在线程
GlobalScope.launch(Dispatchers.Unconfined) {
println("${LocalDateTime.now()}:协程开始运行")
Thread.sleep(2000)
println("${LocalDateTime.now()}:协程结束运行")
}
Thread.sleep(500)//确保协程已经被启动
println("${LocalDateTime.now()}:结束测试")
}
System.out: 2022-10-19T03:12:59.997:开始测试
System.out: 2022-10-19T03:13:00.019:协程开始运行
System.out: 2022-10-19T03:13:02.021:协程结束运行
System.out: 2022-10-19T03:13:02.525:结束测试
这段代码实际上相当于:
fun testBlock(){
println("${LocalDateTime.now()}:开始测试")
println("${LocalDateTime.now()}:协程开始运行")
Thread.sleep(2000)
println("${LocalDateTime.now()}:协程结束运行")
Thread.sleep(500)//确保协程已经被启动
println("${LocalDateTime.now()}:结束测试")
}
我们可以换成delay的方式来堵塞,会更符合我们的预期:挂起协程。
fun testBlock(){
println("${LocalDateTime.now()}:开始测试")
//让协程运行在当前所在线程
GlobalScope.launch(Dispatchers.Unconfined) {
println("${LocalDateTime.now()}:协程开始运行")
delay(2000)
println("${LocalDateTime.now()}:协程结束运行")
}
Thread.sleep(500)//确保协程已经被启动
println("${LocalDateTime.now()}:结束测试")
}
System.out: 2022-10-19T03:50:11.157:开始测试
System.out: 2022-10-19T03:50:11.177:协程开始运行
System.out: 2022-10-19T03:50:11.684:结束测试
System.out: 2022-10-19T03:50:13.183:协程结束运行
协程的取消
情况一:
fun testBlock(){
val job = GlobalScope.launch{
println("${LocalDateTime.now()}:协程开始运行")
Thread.sleep(2000)
println("${LocalDateTime.now()}:协程结束运行")
}
job.cancel()//如果job协程的执行先于cancel,则无法cancel
}
情况二:
fun testBlock(){
val job = GlobalScope.launch{
println("${LocalDateTime.now()}:协程开始运行")
delay(2000)//堵塞2000毫秒
println("${LocalDateTime.now()}:协程结束运行")
}
//在协程delay期间是可以被取消的,后续代码不再执行
job.cancel()
}
挂起函数
挂起函数的应用场景之一在于取代耗时操作后回调的函数,挂起函数的本质上也是回调。挂起函数只能在协程中被调用。
public interface Callback{
public fun onMsg(msg:String);
}
public fun queryMsg(callback:Callback){
Thread{
Thread.sleep(2000);
callback.onMsg("msg")
}.start()
}
可以替换为:
public suspend fun queryMsg():String{
delay(2000);//模拟耗时操作
return "msg";
}
注:挂起函数必须加上suspend关键字,Kotlin 的编译器会将 suspend 函数转换为带有 Callback 的普通函数。