Kotlin协程
背景
在项目实践过程中,笔者发现很多异步或者耗时的操作,都使用了Kotlin中的协程,所以特地研究了一番。
定义
关于协程(Coroutine),其实更多的应该是一种并发设计模式。与传统的线程或进程相比,协程更加轻量级,因为它运行在线程之上,在执行过程中可以主动地暂停,并在需要时恢复执行。而且这些切换操作是在操作系统中的用户态完成的。
实践
- 引入maven依赖:
bash
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
</dependency>
- 两种启动模式
- runBlocking
- GlobalScope.launch
GlobalScope.launch
在 GlobalScope.launch 中启动的协程不会阻塞主线程。主线程会继续执行,而协程会在后台执行。在上述例子中,为了演示效果,使用了 Thread.sleep(2000) 来模拟等待协程执行。
其中,launch 是一个函数,用于创建协程并将其函数主体的执行分派给相应的调度程序。
bash
fun main() {
// 启动协程
GlobalScope.launch {
delay(2000) // 挂起协程,模拟异步操作
println("World!")
}
println("Hello,")
Thread.sleep(4000) // 阻塞主线程,等待协程执行完毕
}
runBlocking
在 runBlocking 中启动的协程会阻塞当前线程,直到所有启动的协程都执行完毕。
bash
fun main() = runBlocking {
// 在 runBlocking 作用域内启动协程
launch {
// 协程代码
println("Inside coroutine")
}
// 主线程继续执行
println("Outside coroutine")
// 阻塞主线程,等待协程执行完毕
}
业务实践
- Dispatchers.IO Kotlin提供的协程调度方式,指示此协程应在为 I/O 操作预留的线程上执行,适合在主线程之外执行磁盘或网络 I/O。
- Dispatchers.Main 使用此调度程序可在主线程上运行协程。(在官方文档介绍中,主要用于安卓开发中,与界面交互和执行快速工作。)
- Dispatchers.Default 适合在主线程之外执行占用大量 CPU 资源的工作。
bash
private fun sendRefundHttpPost(weChatPayRefundUrl: String, xmlRequestData: String): Map<String, String> = runBlocking {
// 启用协程发送退款操作
return@runBlocking withContext(Dispatchers.IO) {
val responseXmlStr = careWeChatHttps.postXmlForString(weChatPayRefundUrl, xmlRequestData)
WXPayUtil.xmlToMap(responseXmlStr)
}
}
bash
private fun getSchoolDate(allTeacherIds: Set<String>): WildSchoolData = runBlocking {
// 涉及统计的allTeacherIds 数量比较大, 创建异步任务去操作
val elements = allTeacherIds.chunked(60).map {
async(Dispatchers.Default) {
val totalCount = teacherPerformanceServiceApi.getTeachersTotalPerformanceCount(it).performanceCount
val currentWeekCount = teacherPerformanceServiceApi.getTeachersCurrentWeekPerformanceCountByByRangeTime(it).performanceCount
WildSchoolData(totalCount, currentWeekCount)
}
}
// 调用await等待异步任务结束
return@runBlocking WildSchoolData(elements.awaitAll().sumBy { it.totalCount }, elements.awaitAll().sumBy { it.currentWeekCount })
}
bash
fun savePoint(pagePointVo: PagePointVo): Any {
// 启用协程执行一个操作流水的插入, 不影响其他操作
GlobalScope.launch(context = Dispatchers.IO) {
val pagePoint = PagePoint().apply {
this.schoolId = pagePointVo.schoolId
this.userId = pagePointVo.userId
this.userRole = pagePointVo.userRole
this.moduleKey = pagePointVo.moduleKey
this.eventId = pagePointVo.eventId
this.clickTime = Date()
}
pagePointMapper.insert(pagePoint)
}
// 其他操作
}