Kotlin-Coroutines

Coroutines中的launch、async和runBloc

2021-02-10  本文已影响0人  From64KB

和线程Thread相比,Kotlin的Coroutines非常的轻量。
开启一个新的Coroutines可以使用launch,async或者runBlocking三个中的一个。不同的第三方块库也会定义其他的启动方法。

fun main() = runBlocking {
    val deferred: Deferred<Int> = async {
        loadData()
    }
    log("waiting...")
    println(deferred.await())
}

suspend fun loadData(): Int {
    log("loading...")
    delay(1000L)
    log("loaded!")
    return 42
}

代码很简单,loadData()模拟了网络请求,但是这里面还有一些东西值得我们具体分析下。首先是上面这段代码是否是非阻塞的?有可能觉得这不废话嘛,用了Coroutine当然是非阻塞的。实际情况在跑一下看下log:

354 [main] INFO  Contributors - waiting...
370 [main] INFO  Contributors - loading...
1376 [main] INFO  Contributors - loaded!
42

注意看[main]这里打印的是当前线程,可以看到所有的代码都是main线程执行的。在给出解决方案之前,先分析下这段代码的执行流程。首先是通过runBlocking{}启动了一个coroutine,这个coroutine是在main线程启动的。然后又通过async{}启动了另一个coroutine,那么这个coroutine是在哪里启动的呢?还是main线程,因为没有明确指定启动线程的情况下coroutine会在外部启动他的coroutine scope中运行(就是runBlocking{})。而一个线程同时只能运行一个coroutine,那么在runBlocking{}启动的coroutine就会进入suspended状态后,启动了async{}启动的coroutine。可以用下面这张图总结下运行状态:

image.png
很明显,所有的代码都是在main线程执行的。那么如何让loadData()在其他线程执行呢?不难想到只要让async{}在其他线程启动就行了。Coroutine是通过指定Dispatcher的方法来指定coroutine运行线程的。代码如下:
fun main() = runBlocking {
    val deferred: Deferred<Int> = async(Dispatchers.Default) {//<-注意看这里的Dispatcher
        loadData()
    }
    log("waiting...")
    println(deferred.await())
    log("finish...")
}

suspend fun loadData(): Int {
    log("loading...")
    delay(1000L)
    log("loaded!")
    return 42
}

指定了async{}Dispatchers.Default来启动,再来看下log:

176 [main] INFO  Contributors - waiting...
176 [DefaultDispatcher-worker-1] INFO  Contributors - loading...
1184 [DefaultDispatcher-worker-1] INFO  Contributors - loaded!
42
1186 [main] INFO  Contributors - finish...

很明显,看到开始和结束都是在main线程中,而模拟加载数据的loadData()运行在DefaultDispatcher-worker-1线程中,这就很符合我们对Coroutine的使用期望了。那么,这个Dispatchers.Default是什么来头?从注释

The default CoroutineDispatcher that is used by all standard builders like launch, async, etc if no dispatcher nor any other ContinuationInterceptor is specified in their context.
It is backed by a shared pool of threads on JVM. By default, the maximal level of parallelism used by this dispatcher is equal to the number of CPU cores, but is at least two. Level of parallelism X guarantees that no more than X tasks can be executed in this dispatcher in parallel.

可以发现这就是一个由CPU核心个数线程构成的线程池(如果是单核的话,最少是两个线程)。所以Kotlin中的Coroutine底层依然是线程池,可以说是太阳底下没有新鲜事。那么用一张图总结下这次的执行流程:

image.png

还有一个问题,如果是要在loadData()中更新主线程UI怎么办?见下面代码:

fun main() = runBlocking {
    val deferred: Deferred<Int> = async(Dispatchers.Default) {
        loadData()
    }
    log("waiting...")
    println(deferred.await())
    log("finish...")
}

suspend fun loadData(): Int {
    log("loading...")
    withContext(Dispatchers.Main){//<---回到主线程
        //update ui here
        log("updating main...")
    }
    delay(1000L)
    log("loaded!")
    return 42
}

执行到 withContext(Dispatchers.Main){}会suspend当前的coroutine直到完成 withContext(Dispatchers.Main){}这里的代码。当然也可以使用launch(context) { ... }.join()来完成相同的功能。

上一篇下一篇

猜你喜欢

热点阅读