[协程]-[详解]-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() 之后, 异常才会被重新抛出.

相关推荐
常利兵5 小时前
一文搞懂双Token、SSO与第三方权限打通,附实战代码
python·gitee·kotlin
PokeMa8 小时前
[协程]-[详解]-[协程作用域]-viewModelScope
kotlin·协程·源码分析·viewmodelscope·原理分析·协程作用域
新缸中之脑9 小时前
使用 AI 进行科学调试
android·人工智能·kotlin
QING61810 小时前
Android Gradle Plugin 9.0 升级指南:告别十年技术债,你准备好了吗?
android·kotlin·gradle
Kapaseker10 小时前
一杯美式理解 Inner Class
android·kotlin
su1ka1111 天前
kotlin(1)介绍
kotlin·intellij-idea
147API1 天前
Claude JSON 稳定输出:Schema 校验与修复回路(Kotlin)
开发语言·kotlin·json·claude
Kapaseker1 天前
解析 Compose 的核心概念 remember
android·kotlin
秋知叶i1 天前
【Android Studio】Kotlin 第一个 App Hello World 创建与运行|超详细入门
android·kotlin·android studio