Coroutines中的launch、async和runBlocking

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

  • async
    async启动新的Coroutine会返回一个Deferred对象。Deferred和线程池中的Future很像,这个对象里可以包含计算的返回值。

  • launch
    launchasync最大的不同点在于launch被用于不关注执行返回结果的计算,概念上和线程池中的Runnable很像。但是launch会返回一个Job对象,这个Job对象表示当前的coroutine,可以用于控制当前计算的生命周期。调用的Job.join()方法可能会阻塞直到launch中的计算完成。
    实际上之前提到的Deferred是继承Job的泛型类,区别在于Deferred中的泛型就是是执行结果类型。可以通过调用Deferred.await()方法获取计算结果,同样的也可控制生命周期。

  • runBlocking
    runBlocking作为普通方法和suspend方法互相调用的桥梁,实现程序在阻塞和非阻塞状态之间切换。通常在main方法和测试代码中用作启动一个顶层的主coroutine。
    概念还是比较抽象,看下面这段代码可以帮助理解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()来完成相同的功能。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容