庖丁解牛,一文搞懂Kotlin协程的常用方法

本篇文章举例协程的各种方法的使用,并简单阐述各个方法的一些注意事项。
协程作用域的创建
1.通过工厂函数创建自定义上下文的作用域
// 举个例子:
// 协程异常处理器(也是一个上下文元素)
private val exceptionHandler =
        CoroutineExceptionHandler { _, throwable ->
            onError(throwable)
        }
val myScope = CoroutineScope(exceptionHandler + SupervisorJob() + Dispatchers.IO)

// 源码
public fun CoroutineScope(
    context: CoroutineContext
): CoroutineScope = ContextScope(
    if (context[Job] != null) context
    else context + Job()
)
    
internal class ContextScope(
    context: CoroutineContext
) : CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    
    override fun toString(): String =
        "CoroutineScope(coroutineContext=$coroutineContext)"
}

2.通过工厂函数MainScope()创建主线程调度器的作用域
// 举个例子:
val mainScope = MainScope()

// 源码
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
3.官方架构提供的一些作用域viewModelScope和lifecycleScope
//  Android JetPack ViewModel里的viewModelScope,ViewModeld的扩展变量
// 来自androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1
public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        
        if (scope != null) {
            return scope
        }
        
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(
                SupervisorJob() +
                Dispatchers.Main.immediate
            )
        )
}

internal class CloseableCoroutineScope(
    context: CoroutineContext
) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    
    override fun close() {
        coroutineContext.cancel()
    }
}

//  Android JetPack lifecycle里的lifecycleScope,LifecycleOwner(Activity实现了此接口,可以直接用)的扩展变量
// 来自androidx.lifecycle:lifecycle-runtime-ktx:2.2.0

val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope

val Lifecycle.coroutineScope: LifecycleCoroutineScope
    get() {
        while (true) {
            val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
            if (existing != null) {
                return existing
            }
            val newScope = LifecycleCoroutineScopeImpl(
                this,
                SupervisorJob() + Dispatchers.Main.immediate
            )
            if (mInternalScopeRef.compareAndSet(null, newScope)) {
                newScope.register()
                return newScope
            }
        }
    }
协程的创建
方式一:launch

创建一个协程,并返回协程的引用job,最常用的方式

val job = MainScope().launch (Dispatchers.IO){
                delay(1000)
                println("run in coroutine")
            }

job.cancel()  // 取消协程
job.join()  // 等待协程执行完毕
job.cancelAndJoin() // 取消并等待协程执行完毕(取消操作并不等于会立马响应,这里其实是分别执行了cancel和join)
job.cancelChildren() // 取消子协程
方式二:async

创建一个协程,并返回协程的引用Deferred(也是个job),通过Deferred的await拿到返回结果,此时协程会挂起

MainScope().launch (Dispatchers.Main){
              // 启动协程
                val deferred = async {
                    "async result"
                }
                // 挂起等待协程结果
                val result = deferred.await()
                // 恢复挂起
                println("result = $result")
   }
作用域构建器
runBlocking

runBlocking {} 会在开启一个新的协程,并且阻塞主线程,直到操作完成。这个函数不应该在协程里面使用,它是用来桥接需要阻塞的挂起函数,主要用于 main function 和 junit 测试。注意他只是一个普通函数。

coroutineScope
        runBlocking {
            val result = coroutineScope {
                println("run thread = ${Thread.currentThread().name}")
                launch {
                    delay(1000)
                    println("run launch success1")
                }
                launch {
                    delay(2000)
                    println("run launch success2")
                }
                "coroutineScope result"
            }
            println("result = $result")
        }
// 运行结果(看得出来使用的是父协程的线程调度器)
run thread = main   
run launch success1
run launch success2
result = coroutineScope result

// 注意这是一个挂起函数,它会挂起执行一直到里面的子协程执行完毕、代码执行完毕然后返回结果,这是他与runBlocking不同的地方,runBlocking只是个普通函数,里面的协程体会阻塞线程。
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return suspendCoroutineUninterceptedOrReturn { uCont ->
        val coroutine = ScopeCoroutine(uCont.context, uCont)
        coroutine.startUndispatchedOrReturn(coroutine, block)
    }
}

coroutineScope经常用来把一个长耗时的任务拆分成多个子任务,使这些子任务并行执行,这就是所谓的结构化并发:

suspend fun showSomeData() = coroutineScope {
    val data1 = async {         //子任务1
        delay(2000)
        100
    }
    val data2 = async {         //子任务2
        delay(3000)
        20
    }
    
    withContext(Dispatchers.Default) {      //合并结果并返回
        delay(3000)
        val random = Random(10)
        data1.await() + data2.await() + random.nextInt(100)
    }
}
supervisorScope

supervisorScope功能与coroutinScope类似,不同之处在于,它可以让里面的子协程发生异常而崩溃的时候不影响自身和其他的子协程的执行。

        runBlocking {
            val result = supervisorScope {
                println("run thread = ${Thread.currentThread().name}")
                launch {
                    delay(1000)
                  // 主动抛异常
                    throw RuntimeException()
                    println("run launch success1")
                }
                launch {
                    delay(2000)
                    println("run launch success2")
                }
                "coroutineScope result"
            }
            println("result = $result")
        }
// 运行结果
run thread = main
run launch success2
result = coroutineScope result

说到supervisorScope顺便提一嘴SupervisorJob,创建作用域或者启动协程的时候如果上下文带上SupervisorJob()也可以达到自身协程处理异常而不影响父协程和兄弟协程的运行。

常用函数
withTimeout
suspend fun timeOut(){
    try {
        withTimeout(1000){
            println("在此做一些耗时操作,超过设定的时间就抛异常")
            delay(2500)
        }
    }catch (e:TimeoutCancellationException){
        println(e.message)
        // 处理超时异常
    }
}
withContext
// 挂起函数,适合一些耗时异步操作
suspend fun timeOut(){
   val result = withContext(Dispatchers.IO){
        delay(2500)
        "result"
    }
println(result)
}
flow

private fun flowEmit() = flow<Int> {
        // 这段代码块实际运行在 flowEmit().collect所处的协程代码块里
        println("...$coroutineContext")
        for (k in 1 .. 3){
            delay(1000)
            emit(k)
        }
    }
    @Test
    fun flowTest() = runBlocking {
        println("...${this.coroutineContext}")
        //  flowEmit()只是创建了个对象
        // 调用collect的时候才开始执行上面定义的代码块
        flowEmit().collect(object : FlowCollector<Int> {
            // 这个代码其实也是运行在runBlocking协程域里
            override suspend fun emit(value: Int) {
                println("...$value")
            }
        })
        //
        println("...end")
    }

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

推荐阅读更多精彩内容