[协程]-[详解]-launch与async

launch

作用

构建一个协程, 直接返回Job

使用

Kotlin 复制代码
// lifecycleScope 协程作用域
lifecycleScope.launch { // 启动一个父协程
    delay(10000) // 模拟任务耗时
}

源码解析

Kotlin 复制代码
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    // 组合上下文
    val newContext = newCoroutineContext(context)
    
    // 根据启动模式创建协程对象
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    
    // 启动协程
    coroutine.start(start, coroutine, block)
    
    // 返回这个协程对象(它本身就是个 Job)
    return coroutine
}

/** 
 * 协程的"基因合成器",它通过 plus 操作符将父 Scope 的上下文与启动参数合并,
 * 并遵循"参数覆盖父辈、默认 Dispatchers.Default 强制补位"的优先级逻辑,
 * 合成出子协程最终的协程上下文。
 */
public fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
    // 将 Scope 的 context 与 传入的 context 合并
    val combined = coroutineContext.foldCopiesForChild() + context
    
    // 注入默认调度器 (Dispatcher)
    val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
    
    // 如果 combined 里没有 Dispatchers,则补上 Dispatchers.Default
    return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
        debug + Dispatchers.Default else debug
}


/**
 * StandaloneCoroutine 定义极其简练,
 * 主要承担 "异常处理终点站" 这一角色
 * 继承关系: StandaloneCoroutine -> AbstractCoroutine -> JobSupport -> Job
 */ 
private open class StandaloneCoroutine(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) {
    
    /**
     * 当协程体抛出异常,状态机会通过 resumeWith(Result.failure) 
     * 回调到 StandaloneCoroutine, 父类 JobSupport 会调用这个 handleJobException,
     */
    override fun handleJobException(exception: Throwable): Boolean {
        /**
         * 会顺着 context 寻找自定义的 CoroutineExceptionHandler
         * 如果自定义了捕获逻辑,就执行自定义的,
         * 如果没有, 就抛给 JVM 的Thread.UncaughtExceptionHandler 将App闪退
         */
        handleCoroutineException(context, exception)
        return true // 告知我已经接受处理这个异常
    }
}

// 源码简化版:AbstractCoroutine.kt
public abstract class AbstractCoroutine<in T>(
    parentContext: CoroutineContext,
    initParentJob: Boolean,
    active: Boolean
) : JobSupport(active), Job, Continuation<T> {

    init {
        // 如果需要初始化父子关系(默认都是 true)
        if (initParentJob) initParentJob(parentContext[Job])
    }

    protected fun initParentJob(parent: Job?) {
        if (parent == null) return // 没父亲(如 GlobalScope),直接返回
        parent.start() // 确保父亲也是启动状态
        
        // 核心:把"自己"交给"父亲"管理,并拿到一个句柄
        val handle = parent.attachChild(this)
        parentHandle = handle // 子协程保存这个句柄,用于后续解除绑定
    }
}

/** 
 * JobSupport 类
 * 将子协程包装成子节点挂载到父协程自己的 NodeList 双向链表中
 */ 
public final override fun attachChild(child: ChildJob): ChildHandle {
    // 1. 创建一个 ChildHandleNode,它是一个双向链表节点
    // 2. 调用 invokeOnCompletion,传入特定的 internal = true 标志
    return invokeOnCompletion(
        onCancelling = true, 
        handler = ChildHandleNode(child).asHandler
    ) as ChildHandle
}

总结

launch 的执行流程大致可以总结如下:

  • 组合上下文
    • 组合参数上下文和父协程上下文
    • 优先级为: 参数上下文(右侧覆盖左侧) > 父协程上下文 > 默认Dispatchers.Default
  • 父子绑定
    • 在实例化 StandaloneCoroutine 时, 父协程 Job 通过attachChild, 将子协程包装成ChildHandleNode, 并挂载到双向链表NodeList中, 实现 "取消向下传播, 异常向上传播" 的能力
  • 分发启动
    • 调用协程 start(), 调度器将协程任务包装成Runnable丢入线程池, 线程执行时, 通过状态机的resumeSuspend 开始逻辑执行
  • 异常报警
    • 若某协程崩溃, StandaloneCoroutine 将立即触发handleJobException, 将任务抛给自定义CoroutineExceptionHandler 或 JVM 的Thread.UncaughtExceptionHandler, 闪退App
  • 完结注销
  • 任务结束后, 通过 parentHandle.dispose 将自己从父协程链表中删除

async

作用

构建一个带返回值的协程, 返回 Deffered, 它继承自 Job, 它不仅可以管理生命周期, 还能通过调用 await() 挂起并获取 "未来" 结果.

使用

Kotlin 复制代码
// lifecycleScope 协程作用域
lifecycleScope.launch { // 启动一个父协程
    val deferred = lifecycleScope.async {
        delay(10000)
        "Success" // 返回结果
    }
    val a = deferred.await() // 挂起并等待结果
    Log.d("--", "a=${a}")
}

源码解析

Kotlin 复制代码
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    // 组合上下文(同 launch)
    val newContext = newCoroutineContext(context)
    // 创建协程对象:DeferredCoroutine 实例
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    // 启动协程
    coroutine.start(start, coroutine, block)
    return coroutine
}

/**
 * DeferredCoroutine 只实现了 await, 但未实现 handleJobException
 * 继承关系: DeferredCoroutine -> AbstractCoroutine -> JobSupport -> Job
 */ 
private open class DeferredCoroutine<T>(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine<T>(parentContext, initParentJob = true, active = active), Deferred<T> {
    
    // 调用 await 时进入 JobSupport 的内部逻辑
    override suspend fun await(): T = awaitInternal() as T
}

/** await 内部方法 */
internal suspend fun JobSupport.awaitInternal(): Any? {
    while (true) {
        val state = this.state
        // 如果已完成,直接返回结果(或抛出异常)
        if (state !is Incomplete) return state.makeQueryState()
        // 如果未完成,尝试进入等待状态
        if (startAwaitOrJoin(state) >= 0) break
    }
    // 正式挂起,直到结果就绪被唤醒
    return awaitSuspend()
}


private suspend fun awaitSuspend(): Any? = suspendCoroutineUninterceptoredOrReturn { uCont ->
    // 包装当前 Continuation 为 AwaitContinuation 节点
    val waiter = AwaitContinuation(uCont.intercepted(), this)
    // 将这个"等待节点"也挂载到 JobSupport 的双向链表 NodeList 中
    val handle = invokeOnCompletion(handler = waiter)
    COROUTINE_SUSPENDED
}

总结

async 的底层逻辑大致如下:

  • 组合上下文
    • 组合参数上下文和父协程上下文
    • 优先级为: 参数上下文(右侧覆盖左侧) > 父协程上下文 > 默认Dispatchers.Default
  • 父子绑定
    • 在实例化 DeferredCoroutine 时, 父协程 Job 通过attachChild, 将子协程包装成ChildHandleNode, 并挂载到双向链表NodeList中, 实现 "取消向下传播, 异常向上传播" 的能力
  • 分发启动
    • 调用协程 start(), 调度器将协程任务包装成Runnable丢入线程池, 线程执行时, 通过状态机的resumeSuspend 开始逻辑执行
  • 挂起等待
    • 当开发者调用 await() 时, 如结果已就绪则返回, 结果未就绪, 则将 Continuation 包装成 AwaitContinuation 节点, 然后挂载到 双向链表 NodeList 中
  • 结果存入与唤醒
    • 当 async 协程逻辑跑完, AbstractCoroutine 内会将结果存入 JobSupport 的状体位, 并遍历NodeList, 找到 AwaitContinuation , 通过其内部持有的 Continuation 执行 resume(result), 唤醒挂起点.
  • 完结注销
  • 任务结束后, 通过 parentHandle.dispose 将自己从父协程链表中删除

两者区别

相同点

第一: 协程底子相同

虽然两者对应协程类不同, ++launch是StandaloneCoroutine++ , ++async是DeferredCoroutine++, 但都继承自 AbstractCoroutine, 都拥有协程生命周期管理等 AbstractCoroutine 拥有的能力

第二: 上下文组合方式相同

遵循的组合优先级都是: 参数上下文(右侧覆盖左侧) > 父上下文 > 默认协程调度器

第三: 结构化并发相同

都通过 attachChild 将子协程包装成子节点, 挂载到父协程的双向链表 NodeList 中
默认情况下都遵循 "父协程取消, 向下递归子协程取消"、"子协程崩溃, 向上通知父协程取消"

第四: 分发逻辑相同

都依赖 CoroutineDispatcher 进行任务分发, 启动后都会封装成 DispatcherContinuation 丢入线程池排队执行.

不同点

两者区别主要在: 返回值、异常处理机制

第一: 返回值不同

launch 返回Job, 用于执行不需要结果的任务
async 返回 Deferred, 用于执行需要等待异步结果的任务

第二: 异常处理机制不同

launch 协程是StandaloneCoroutine, 它实现了handleJobException, 协程任务如果发生崩溃, 崩溃会即刻向上传播到自定义的 ++CoroutineExceptionHandler++ 或通过 JVM 的 ++Thread.UncaughtExceptionHandler++ 崩溃App.
async 协程是DeferredCoroutine, 未实现handleJobException, 任务如果发生崩溃, 不会立即处理, 而是会等到开发者执行了 Deferred.await() 之后, 异常才会被重新抛出.

相关推荐
limuyang24 小时前
PDF Viewer KMP(基于 chrome 的 PDFium 内核)
android·kotlin
pengyu11 小时前
【Kotlin 协程修仙录 · 大乘境 · 初阶】 | 跳出三界:协程在 KMP 与后端开发中的跨平台之道
android·kotlin
XiaoLeisj15 小时前
Jetpack Compose 知识点
android·kotlin·android jetpack·compose
Kapaseker20 小时前
Compose SelectionState API — 主动控制文本选择
android·kotlin
小强闯江湖1 天前
不只是让 AI 写代码:ViewCompose 把 Android UI 做成了可编译、可渲染的闭环
android·人工智能·kotlin
bytebitx3 天前
MacOS 编译腾讯 Mars XLog 及使用(已16KB对齐)
kotlin·mac·apk
alexhilton3 天前
让架构边界变成可执行的测试
android·kotlin·android jetpack
数据治理自习室4 天前
AI 应用评测体系(GraphRAG)
android·大数据·人工智能·kotlin
名剑走天下5 天前
Android 面试题大全
android·kotlin
chjif5 天前
Android 设备管控开发实战:自定义 Launcher 如何只显示指定 App?
android·kotlin