初遇Kotlin协程

初遇Kotlin协程(coroutine)

这篇文章我们将建立协程项目,并用Coroutines编写相关代码。

Kotlin 1.1引入了协程程序,这是一种编写异步、非阻塞代码(以及其他)的新方法。在这篇文章中,我们将使用kotlinx.coroutines库来了解基本的协程写法,这个库是对已存的JAVA库的封装。

Setting up a project

我们将使用Gradle来构建项目。
加入库依赖:

dependencies {
    ...
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1"
}

这个库托管在JCenter仓库中,我们加入仓库地址:

repositories {
    jcenter()
}   
Coroutine第一行代码

我们可以认为协程是一种轻量级的线程。像线程一样,协程可以并行,可以相互间通信。和线程最大的不同是,协程是非常轻量级的,我们可以创建几千个协程,但是消耗的性能却非常非常的少。而对于真的线程,这是非常耗资源的。几千个线程对于现代计算机来说是一个严重的挑战。
我们怎么启动一个协程呢?可以使用 launch{}函数:

launch{
...
}  

完整的例子如下:

println("Start")
GlobalScope.launch {
    delay(1000)
    println("Hello")
}
Thread.sleep(2000) // wait for 2 seconds
println("Stop")

这里我们启动了一个协程,等待一秒后,打印了 Hello
我们使用了 delay() 这个方法,这个方法类似 Thread.sleep() ,但是更好,它不阻塞线程,只是挂起协程本身。当协程挂起等待是,返回到线程;当协程等待完成时,协程恢复继续运行。

在这个例子中,主线程 main() 必须等待协程完成,否则在输出 Hello 之前,程序就结束了。

假如我们在 main() 中直接使用 delay() 函数,将会遇到编译错误:

Suspend functions are only allowed to be called from a coroutine or another suspend function

这是因为挂起函数只能运行在协程中,我们可以使用 runBlocking() 来启动一个协程。

runBlocking {
    delay(2000)
}
复杂一点的例子

让我们来看下协程是不是轻量级的,我们启动一百万个线程:

val c = AtomicLong()

for (i in 1..1_000_000L)
    thread(start = true) {
        c.addAndGet(i)
    }

println(c.get())

这个例子将会运行较长一段时间,消耗较多的资源。:(
我们换种方式,用协程来实现:

val c = AtomicLong()

for (i in 1..1_000_000L)
    GlobalScope.launch {
        c.addAndGet(i)
    }

println(c.get())

这个例子几秒就完成了,对比线程,有着显著的优势。

Async: 从协程中返回值

另一种启动协程的方法是使用 async{} ,它类似 launch{},但是它返回 Deferred 实例,这个实例有 await() 方法,该方法返回协程结果。

我们启动一百万个协程,然后持有它们的返回结果 Deferred

val deferred = (1..1_000_000).map { n ->
    GlobalScope.async {
        n
    }
}

这些协程都已经启动,我们把它们加起来。

val sum = deferred.sumBy { it.await() }

我们使用了标准函数库 sumBy,来把他们加在一起,但是我们简单这样做,编译器会报错:

Suspend functions are only allowed to be called from a coroutine or another suspend function

因为 await() 是挂起函数,不能用在协程外面,正如上面说过的一样。我们把它放在协程里:

runBlocking {
    val sum = deferred.sumBy { it.await() }
    println("Sum: $sum")
}

现在它将会顺利输出结果。我们稍微改下代码,确认这个百万个协程是平行的,假如我们再每个启动的协程里延时一秒,看看是否要花费百万秒才会输出结果:

val deferred = (1..1_000_000).map { n ->
    GlobalScope.async {
        delay(1000)
        n
    }
}

我们可以运行下,就可以知道结果。结果是只用了十几秒。显然,它是并行的。

Suspending functions

现在我们把里边的代码提取出来:

fun workload(n: Int): Int {
    delay(1000)
    return n
}

一个类似的错误将会出现:

    Suspend functions are only allowed to be called from a coroutine or another suspend function

让我们进一步看看这个是什么意思。协程最大的优势是可以不阻塞线程的挂起。编译器将会组织特殊的代码来达到这个可能,所以我们使用 suspend 来修饰一个方法:

suspend fun workload(n: Int): Int {
    delay(1000)
    return n
}

现在我们可以在协程种调用 workload 方法,编译器知道这个方法可能会挂起,并做相应的工作。

GlobalScope.async {
    workload(n)
}

我们的 workload 方法能够在协程中被调用,或者别的挂起函数,但是不能够在协程外调用。相应的,delay()await() 函数被 suspend 修饰,这就是为什么它们只能在挂起函数中被调用,或者在协程内被调用,runBlocking(), launch{}, 或者 async()

你可能感兴趣的:(初遇Kotlin协程)