协程(英文Coroutine)并非一个新概念,早在上世纪60年代Cobol编译器中被首次提出,其目的是将程序分离成多个任务,可以相互配合并且独立运行,co-routine的名字也正体现了互相配合之意。
协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。
协程的关键是分清挂起和阻塞的区别,通过和Future的对比,我们可以更好的理解挂起的含义。
// 以Coroutine的方式运行
suspend fun loadProfile(id) {
val profile = getProfile(id).await()// --- > 协程被挂起,等待getProfile的完成
// 挂起不会阻塞当前线程
showProfile(profile) // <---- getProfile返回结果,协程继续
}
// 以Future方式运行
fun loadProfile(id) {
val profile = getProfile(id).get() // --- > 线程被阻塞,等待getProfile的完成
// 阻塞期间,线程不能有效利用
showProfile(profile) // < --- getProfile返回结果,线程继续
}
即使不使用协程,通过回调也可以在不阻塞线程的情况下保证执行顺序,这种通过回调传递结果的方式在Java中也被称为CPS(Continuation-Passing-Style)
fun getProfile(id: Int, f: (Profile) -> Unit)
fun loadProfile(id: Int) {
getProfile(id) {
showProfile(profile)
}
}
以上代码无法很好地体现协程相对于CPS的优势,但是随着程序越来越复杂,过多的回调带来的缩进会导致代码可读性的降低
fun loadProfile(id: Int) {
getProfile(id) { profile ->
getReport(profile.id) { report ->
//... do sth with report
}
}
}
如果使用协程,则可以用顺序的代码完成同样的逻辑,大大提高了代码的可读性
suspend fun loadProfile(id: Int) {
val profile = getProfile(id).await()
val report = getReport(profile.id).await()
// ... do sth with report
}