Kotlin1.1的时候介绍了协程,一种写异步的非阻塞的新方法,使用协程我们要引入kotlinx.coroutines库。
集成步骤
1.确保工程配置为kotlin1.1或者更高版本
2.在build.gradle中添加如下代码
apply plugin: 'kotlin'
kotlin {
experimental {
coroutines 'enable'
}
}
注:如果项目中引用了apply plugin: 'kotlin-android',就不用引用apply plugin: 'kotlin'
3.由于android中使用kotlinx.coroutines,我们将它的最新版本添加到依赖项中
dependencies {
...
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.21'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.21'
}
4.添加混淆
在混淆代码中,具有不同类型的字段可以具有相同的名称,并且AtomicReferenceFieldUpdater可能无法找到正确的字段。要避免在混淆期间按类型进行字段重载,请将其添加到配置中:
-keepclassmembernames class kotlinx.** {
volatile ;
}
第一个协程
我们可以把协程认为是一个轻量的线程。像线程一样,协程同样可以并行运行,彼此等待并进行通信。协程和线程最大的不同就是,协程很轻量,我们可以创建上千个,并且只消耗很少的性能。线程从开始到保持都要耗费很多资源,而且对现在机器来说上千个线程是一个很严峻的挑战。我们可以通过launch{}方法开启一个协程,默认情况下协程运行在一个共享的线程池上。线程仍然可以运行在一个基于协程开发的程序中,一个线程可以运行很多个协程,所以我们将不再需要很多的线程。示例如下:
import kotlinx.coroutines.experimental.*
fun main(args: Array) {
println("Start")
// Start a coroutine
launch {
delay(1000)
println("Hello")
}
Thread.sleep(2000) // wait for 2 seconds
println("Stop")
}
运行结果:
说明:上述代码中我们开启了一个协程,一秒后打印hello。我们使用delay()方法,就像使用Thread.sleep()方法,但是delay方法会更好一些,它不会阻塞线程,它只是暂停协程本身。当协程正在等待时,线程返回到池中,并且当等待完成时,协程将在池中的空闲线程上恢复。
如果你想在main函数中使用非阻塞的delay方法,会发生一个编译错误Suspend functions are only allowed to be called from a coroutine or another suspend function,因为我们没有在协程中执行,我们将它包装在runBolcking{}中使用。runBlocking{}会启动协程并等待协程执行完成
import kotlinx.coroutines.experimental.*
fun main(args: Array) {
println("Start")
// Start a coroutine
launch {
delay(1000)
println("Hello1")
}
runBlocking {
delay(2000)
println("Hello2")
}
Thread.sleep(2000) // wait for 2 seconds
println("Stop")
}
对比线程和协程所消耗的资源
执行如下代码:
fun main(args: Array) {
createThreads()
createCoroutines()
}
fun createCoroutines() {
thread(start = true) {
val c = AtomicInteger()
for (i in 1..1_000_000)
launch {
c.addAndGet(i)
}
println("${c.get()},coroutines")
}
}
fun createThreads() {
thread(start = true) {
val c = AtomicInteger()
for (i in 1..1_000_000)
thread(start = true) {
c.addAndGet(i)
}
println("${c.get()},threads")
}
}
结果:
说明:从运行结果上分析,执行协程要比线程轻量很多,但是它打印出了一些任意数字,这是因为有一些协程在输出结果的时候还没有运行结束。让我们在下一个小节对这个问题进行修复。
Async:从协程中返回来一个值
还有一个开启协程的方式就是async{},和lauch()一样,但是它会返回一个Deferred
让我们再次创建百万个协程,保留它们的Deferred对象。现在就不需要原子计数器了,我们只需要返回协程中添加的数字。
fun main(args: Array) {
createAsyncCoroutines()
}
fun createAsyncCoroutines() {
thread(start = true) {
val deferred = (1..1_000_000).map { n ->
async {
n
}
}
runBlocking {
val sum = deferred.sumBy { it.await() }
println("Sum: $sum")
}
}
}
注:await()不能在协程之外使用,因为它需要挂起直到计算完成,并且只有协程能过在非阻塞的方式挂起。现在输出结果是1784293664,因为所有协程都执行完毕了,运行结果如下:
结果:
我们一样可以证明协程是并行运行的。如果我们使用delay函数,给每个async加上一秒延时。程序运行结果并不是1_000_000秒(大概11.5天)
val deferred = (1..1_000_000).map { n ->
async {
delay(1000)
n
}
}
挂起函数
假如说我们要实现一个单独的方法,这个方法等待1秒并且返回一个数字
suspend fun workload(n: Int): Int {
delay(1000)
return n
}
注:协程最大的价值就是他可以挂起而不阻塞线程。编译器通过suspend关键字实现这一功能
如果我们现在执行workload方法的话,编译器就会知道这个方法可以挂起,并且做相应准备。
async {
workload(n)
}
我们的workload()函数可以从协程(或其他挂起函数)调用,但不能从协程外部调用。当然,我们上面使用的delay()和await()本身也被声明为suspend,这就是我们必须将它们放在runBlocking {},launch {}或async {}中的原因。