Kotlin 协程协程启动 ④ ( 协程启动模式 | 协程构建器启动模式参数 | DEFAULT 模式 | ATOMIC 模式 | LAZY 模式 | UNDISPATCHED 模式 )

Posted 韩曙亮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin 协程协程启动 ④ ( 协程启动模式 | 协程构建器启动模式参数 | DEFAULT 模式 | ATOMIC 模式 | LAZY 模式 | UNDISPATCHED 模式 )相关的知识,希望对你有一定的参考价值。

文章目录





一、协程构建器 CoroutineScope.async 函数



1、协程构建器 CoroutineScope.async 函数参数分析


协程构建器 CoroutineScope.async 函数中 ,

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T>
  • 第一个参数 context: CoroutineContext = EmptyCoroutineContext 是协程的上下文对象 ;
  • 第二个参数 start: CoroutineStart = CoroutineStart.DEFAULT 是协程的启动模式 ;
  • 第三个参数 block: suspend CoroutineScope.() -> T 是协程作用域代码块 , 其中是协程任务代码 ;

2、协程构建器 CoroutineScope.async 函数参数原型


CoroutineScope.async 函数原型 : 机翻文档 , 仅供参考 ;

// --------------- async ---------------

/**
 * 创建协程并将其未来结果作为[Deferred]的实现返回。
 * 当产生的延迟为[cancelled][Job.cancel]时,正在运行的协程将被取消。
 * 得到的协程与其他语言中的类似原语相比有一个关键的区别
 * 和框架:它取消父作业(或外部作用域)在执行*结构化并发*范式失败。
 * 要改变这种行为,可以使用监督父级([SupervisorJob]或[supervisor orscope])。
 *
 * 协程上下文从[CoroutineScope]继承,附加的上下文元素可以用[context]参数指定。
 * 如果上下文没有任何dispatcher,也没有任何其他[ContinuationInterceptor],则[Dispatchers.]默认使用“Default”。
 * 父作业也从[CoroutineScope]继承,但它也可以被覆盖
 * 使用相应的[上下文]元素。
 *
 * 默认情况下,协程是立即调度执行的。
 * 其他选项可以通过' start '参数指定。参见[coroutinstart]了解详细信息。
 * 可选参数[start]可以设置为[coroutinstart]。启动协同程序。在这种情况下,
 * 结果[Deferred]是在_new_状态下创建的。它可以显式地以[start][Job.start]开始
 * 函数,并将在第一次调用[join][Job时隐式启动。加入],[等待][递延。await]或[awaitAll]。
 *
 * @param block 协同程序代码。
 */
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> 
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine





二、协程启动模式



协程启动模式 :

  • DEFAULT 模式 : 默认的 协程启动模式 , 协程创建后 , 马上开始调度执行 , 如果在 执行前或执行时 取消协程 , 则进入 取消响应 状态 ;

1、DEFAULT 模式


DEFAULT 模式 : 默认的 协程启动模式 , 协程创建后 , 马上开始调度执行 , 如果在 执行前或执行时 取消协程 , 则进入 取消响应 状态 ; 如果在执行过程中取消 , 协程也会被取消 ;


代码示例 : DEFAULT 模式的协程可以被取消 ;

runBlocking 
    // 调用 runBlocking 函数 , 可以将 主线程 包装成 协程
    
    // 指定协程的启动模式为 CoroutineStart.DEFAULT
    // 默认的 协程启动模式 , 协程创建后 , 马上开始调度执行 ,
    // 如果在调度前取消协程 , 则进入 取消响应 状态 ;
    val job = launch (start = CoroutineStart.DEFAULT) 
        Log.i(TAG, "协程开始执行")
        delay(2000)
        Log.i(TAG, "协程执行完毕")
    
    delay(1000)
    // 取消协程
    job.cancel()

执行结果 :

00:44:46.329  I  协程开始执行
00:44:48.372  I  协程执行完毕

如果协程没有执行完 , 就取消协程任务 , 则协程会被中途取消 ;


协程是 基于线程 的 , 线程 由 调度器 控制 , 线程包含主线程和子线程 ;

上述代码中 , 调用 runBlocking 函数 , 可以将 主线程 包装成 协程 , launch 启动协程 , 该协程运行在主线程中 ,

运行到 delay(2000) 代码时 , 该 delay 函数是挂起函数 , 主线程会被挂起 , 主线程被调度器调度 , 执行其它的操作 如 刷新 UI 等操作 , 挂起函数中的内容会在子线程中执行 ,

如果 launch 启动协程时 , 此时会被调度器 立即调度 , 但是 主线程不会立即执行 , 如 主线程正在执行 刷新 UI 等任务 , 此时如果取消该协程 , 则协程直接取消 ;


如果在主线程中执行协程 , 协程挂起后 , 主线程继续执行其它任务, 如刷新 UI 等 , 主线程不会阻塞 , 挂起函数会在子线程中执行 ;

一般会将耗时操作放在 协程的挂起函数 中执行 ;


2、ATOMIC 模式


ATOMIC 模式 : 协程创建后 , 马上开始调度执行 , 协程执行到 第一个挂起点 之前 , 如果取消协程 , 则不进行响应取消操作 ;

代码示例 : 在下面的代码中 , 协程执行后 , 遇到的 第一个挂起函数是 delay(2000) 函数 , 该 挂起函数之前的代码执行过程中 , 如果取消协程 , 则该 协程不会取消 , 直到执行到 第一个挂起函数是 delay(2000) 函数 时 , 协程才会取消 ;

runBlocking 
    // 调用 runBlocking 函数 , 可以将 主线程 包装成 协程
    // 指定协程的启动模式为 CoroutineStart.ATOMIC 
    // 协程创建后 , 马上开始调度执行 , 
    // 协程执行到 第一个挂起点 之前 , 如果取消协程 , 则不进行响应取消操作 ;
    val job = launch (start = CoroutineStart.ATOMIC) 
        Log.i(TAG, "协程开始执行")
        delay(2000)
        Log.i(TAG, "协程执行完毕")
    
    // 取消协程
    job.cancel()


3、LAZY 模式


ATOMIC 模式 : 协程创建后 , 不会马上开始调度执行 , 只有 主动调用协程的 start , join , await 方法 时 , 才开始调度执行协程 , 如果在 调度之前取消协程 , 该协程直接报异常 进入异常响应状态 ;

代码示例 : 在下面的代码中 , val job = async (start = CoroutineStart.LAZY) 只是定义协程 , 并不会马上执行 , 在执行 job.start()job.await() 代码时 , 才开始调度执行协程 , 如果在这之前调用 job.cancel() 取消协程 , 则协程直接取消 ;

runBlocking 
    // 调用 runBlocking 函数 , 可以将 主线程 包装成 协程
    // 指定协程的启动模式为 CoroutineStart.LAZY
    // 协程创建后 , 不会马上开始调度执行 ,
    // 只有 主动调用协程的 start , join , await 方法 时 , 才开始调度执行协程 ,
    // 如果在 调度之前取消协程 , 该协程直接报异常 进入异常响应状态 ;
    val job = async (start = CoroutineStart.LAZY) 
        Log.i(TAG, "协程开始执行")
        delay(2000)
        Log.i(TAG, "协程执行完毕")
        "Hello" // 返回一个字符串
    
    delay(1000)
    
    // 取消协程 , 在调度之前取消 , 协程直接进入异常响应状态 
    //job.cancel()
    
    // 执行下面两个方法中的任意一个方法 ,
    // 启动执行协程
    job.start()
    // 获取协程返回值
    job.await()


4、UNDISPATCHED 模式


UNDISPATCHED 模式 : 协程创建后 , 立即在当前的 函数调用栈 执行协程任务 , 直到遇到第一个挂起函数 , 才在子线程中执行挂起函数 ;

  • 如果在主线程中启动协程 , 则该模式的协程就会直接在主线程中执行 ;
  • 如果在子线程中启动协程 , 则该模式的协程就会直接在子线程中执行 ;

代码示例 : Dispatchers.IO 调度器是将协程调度到子线程执行 , 但是如果 协程启动模式为 UNDISPATCHED , 则 立刻在当前的主线程中执行协程 , 协程创建后 , 立即在当前的 函数调用栈 执行协程任务 , 打印当前线程时 会打印主线程 ;

runBlocking 
    // 调用 runBlocking 函数 , 可以将 主线程 包装成 协程
    // 指定协程的启动模式为 CoroutineStart.UNDISPATCHED
    // 协程创建后 , 立即在当前的 函数调用栈 执行协程任务 ,
    // 直到遇到第一个挂起函数 , 才在子线程中执行挂起函数 ;
    val job = async ( context = Dispatchers.IO, start = CoroutineStart.UNDISPATCHED ) 
        // Dispatchers.IO 调度器是将协程调度到子线程执行
        // 但是如果 协程启动模式为 UNDISPATCHED , 则立刻在当前的主线程中执行协程
        // 此时打印出主线程 ,
        // 协程创建后 , 立即在当前的 函数调用栈 执行协程任务 , 因此会打印主线程
        Log.i(TAG, "协程开始执行 当前线程 : $Thread.currentThread()")
        // 遇到挂起函数会在子线程执行该挂起函数
        // 挂起函数都是耗时任务
        delay(2000)
        Log.i(TAG, "协程执行完毕")
        "Hello" // 返回一个字符串
    


5、CoroutineStart 中定义的协程启动模式原型


机翻文档 , 仅供参考 ;

package kotlinx.coroutines

import kotlinx.coroutines.CoroutineStart.*

/**
 * 定义协同程序构建器的开始选项。
 * 它用于[launch][CoroutineScope的' start '参数中。发射],[异步][CoroutineScope。以及其他协程构建器函数。
 *
 * 协程启动选项的汇总如下:
 * * [DEFAULT]——根据上下文立即安排协程执行;
 * * [LAZY]—只在需要时才启动协程;
 * * [ATOMIC]——原子地(以不可取消的方式)根据上下文安排协程执行;
 * * [UNDISPATCH]——立即执行协程,直到它在当前线程中的第一个挂起点_。
 */
public enum class CoroutineStart 
    /**
     * Default——根据上下文立即安排协程执行。
     *
     * 如果协程上下文的[CoroutineDispatcher]从[CoroutineDispatcher. isdispatchneeded]返回' true '
     * 像大多数调度程序那样运行,那么协程代码稍后被调度执行,而代码则被调度执行
     * 调用的协程构建器继续执行。
     *
     * 注意[Dispatchers.]总是从它的[CoroutineDispatcher.isDispatchNeeded]返回' false '
     * 函数,因此启动与[Dispatchers.]的协程。使用[DEFAULT]与使用[undispatch]相同。
     *
     * 如果协程[Job]在它甚至有机会开始执行之前被取消,那么它将不会启动它的
     * 执行,但将以异常完成。
     *
     * 协程在挂起点的可取消性取决于的具体实现细节
     * 暂停功能。使用[suspendCancellableCoroutine]实现可取消的挂起函数。
     */
    DEFAULT,

    /**
     * 只有在需要时才会惰性地启动协程。
     *
     * 有关详细信息,请参阅相应协程构建器的文档
     * (如[发射][CoroutineScope。和[async][CoroutineScope.async])。
     *
     * 如果协程[Job]在它甚至有机会开始执行之前被取消,那么它将不会启动它的
     * 执行,但将以异常完成。
     */
    LAZY,

    /**
     * 原子地(即,以一种不可取消的方式)根据上下文安排协程的执行。
     * 这类似于[DEFAULT],但是协程在开始执行之前不能被取消。
     *
     * 协程在挂起点上的可取消性取决于的具体实现细节
     * suspend功能如[DEFAULT]。
     */
    @ExperimentalCoroutinesApi // Since 1.0.0, no ETA on stability
    ATOMIC,

    /**
     * 立即执行协程,直到它在当前线程中的第一个挂起点_
     * 正在使用[dispatchers . unrestricted]启动协程。但是,当从挂起恢复协程时
     * 它根据上下文中的[CoroutineDispatcher]进行分派。
     *
     * 这与[ATOMIC]在某种意义上类似,协程开始执行,即使它已经被取消,
     * 但不同的是,它在同一个线程中开始执行。
     *
     * 协程在挂起点上的可取消性取决于的具体实现细节
     * suspend功能如[DEFAULT]。
     *
     * 无限制事件循环
     *
     * 与调度程序。和[MainCoroutineDispatcher。],嵌套的未分派协程不会形成
     * 在无限制嵌套的情况下防止潜在堆栈溢出的事件循环。
     */
    UNDISPATCHED;

    /**
     * 当[LAZY]时返回' true '。
     *
     * @suppress **这是一个内部API,不应该从通用代码中使用
     */
    @InternalCoroutinesApi
    public val isLazy: Boolean get() = this === LAZY

以上是关于Kotlin 协程协程启动 ④ ( 协程启动模式 | 协程构建器启动模式参数 | DEFAULT 模式 | ATOMIC 模式 | LAZY 模式 | UNDISPATCHED 模式 )的主要内容,如果未能解决你的问题,请参考以下文章

深入理解Kotlin协程协程作用域启动模式调度器异常和取消使用篇

Kotlin 协程协程启动 ⑤ ( 协程作用域构建器 | runBlocking 函数 | coroutineScope 函数 | supervisorScope 函数 )

Kotlin 协程协程启动 ⑤ ( 协程作用域构建器 | runBlocking 函数 | coroutineScope 函数 | supervisorScope 函数 )

Kotlin 协程协程启动 ① ( 协程构建器 | launch 构建器 | async 构建器 | runBlocking 函数 | Deferred 类 )

Kotlin 协程协程启动 ① ( 协程构建器 | launch 构建器 | async 构建器 | runBlocking 函数 | Deferred 类 )

Kotlin 协程协程底层实现 ④ ( 结构化并发 | viewModelScope 作用域示例 )