图片来自必应
本文是对官方文档中协程的教程的翻译加上个人理解,也可以直接阅读官方文档:Your first coroutine with Kotlin
协程可以认为是一个轻量级的线程,和线程一样,它可以同时运行、等待运行或者马上运行。它与线程最大的不同在于协程的开销非常低,几乎不需要开销。我们可以创建数千个协程,并且只付出很少的性能损耗。从另一方面来说,真正的线程去开启并且运行它是十分昂贵的,数千个线程对现代机器的性能来说是个十分严峻的挑战。
- 引入协程
引入协程的方法很简单,只需要在app的build.gradle 文件中引入coroutine支持:
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'
}
复制代码
那么我们如何开始使用协程? 让我们来看看 lunch{}函数:
GlobalScope.lunch{
...
}
复制代码
上述代码将会开启一个新的协程, GlobalScope
表示该协程的生命周期仅受整个应用的生命周期影响。当然我们也可以创建一个新的协程基于某一个线程的生命周期,例如:
CoroutineScope(newSingleThreadContext("thread-1")).launch { }
复制代码
在默认情况下,协程会运行在线程共享池。在基于协程的程序中线程仍然会存在,但是一个线程能够运行很多协程,所以我们并不需要创建很多的线程, 我们来看使用协程的一整段代码:
fun main(args: Array<String>){
println("Start")
GlobalScope.launch{
delay(1000)
println("Hello")
}
Thread.sleep(2000)
println("Stop")
}
复制代码
从上面的代码可以看出来,我们可以使用delay()
函数类似Thread()
类中的 sleep()
方法,但是这个方法的好处在于:它并不会像Thread().sleep()
那样会阻塞线程,而仅仅是暂停当前的协程。当协程暂停的时候,当前的线程将会释放,当协程暂停结束的时候,它将会在线程池中的空闲的线程上恢复,这样就意味着,如果我们使用协程,我们就可以不用像线程那样去使用回调处理返回结果,虽然RxJava可以做到等待结果返回,但是也没有协程这样方便简洁
如果在主线程的话,必须要等待我们协程完成,否则上面的例子将会在“Hello”打印之前结束了。
让我们将上面的Thread().sleep(2000)
这句代码注释掉,那么结果将会是先打印“Stop”,再打印“Hello”。
如果我们直接使用同样的非阻塞方法 delay()
在主线程内,将会出现编译错误:
Suspend functions are only allowed to be called from a coroutine or another suspend function
这个错误是因为我们使用了delay( )
而没有在任何的协程中,我们可以通过runBlocking{}
来启动一个协程并且阻塞直到其完成:
runBlocking{
delay(1000)
}
复制代码
现在,对于上面的例子来说,首先会打印“Start” ,然后会运行到launch{}
,然后会运行runBlocking{}
直到它完成,然后打印“Stop”,与此同时第一个协程完成并且打印“Hello”。
- async: 返回协程的值
还有一种开启一个协程的方法为async{}
, 在这方面它和launch{}
具有一样的效果,但是async{}
会返回一个Deferred<T>
的实例,这个实例有一个方法await()
,这个方法可以返回协程的结果。
让我们再来看一段代码,先来运行一百万个协程,并且将它们返回的Deferred
对象保存起来。然后计算结果:
val deferred = (1..1_000_000).map{
n -> async{
n
}
}
复制代码
当所有的都启动了之后,我们显然需要收集它们的结果:
val sum = deferred.sumBy{it.await()}
复制代码
上面的代码看上去好像没有什么问题,我们把每个协程的结果拿到之后对其求和,看上去好像一切正常,但是编译器却报错了:
Suspend functions are only allowed to be called from a coroutine or another suspend function
复制代码
显然,await()
不能够被使用在协程之外,因为await()
会暂停协程知道它完成,然而只有协程能够被不阻塞的暂停,所以,我们应该将await()
写在协程里面:
runBlocking{
val sum = deferred.sumBy{it.await()}
println("Sum: $sum")
}
复制代码
- 挂起函数(Suspending functions)
正如文章开头提到的,协程最大的优点就是可以不通过阻塞而挂起线程,编译器必须要通过一些特殊的代码而去实现这个功能,所以我们必须要显式的说明那些可能会挂起(suspend)的代码,所以可以使用suspend去说明:
suspend fun workload(n: Int): Int{
delay(1000)
return n
}
复制代码
当我们使用suspend显式的说明workload()
函数可能会suspend之后,当我们从协程中调用它,编译器就会知道这个函数将会suspend并且做好相应的准备:
async{
workload(n)
}
复制代码
这时workload(n)
将能够从协程或者其他的suspend函数中调用,但是不能够在协程以外调用。相应的,delay()
和 await()
是被默认声明为suspend的, 这就是为什么必须要在runBlocking{}
、launch{}
、async{}
中才能够调用它们的原因。
以上就是kotlin协程中一些基本概念与使用,关于协程的更多用法会在之后的文章中再一一说明。