Kotlin协程Context应用使用示例详解

目录
  • 1.Context的应用
  • 2.万物皆有 Context
    • 1.CoroutineScope
    • 2.Job
    • 3.Dispatcher
    • 4.CoroutineExceptionHandler

1.Context的应用

Context在启动协程模式中就已经遇到过叫CoroutineContext,它的意思就是协程上下文,线程的切换离不开它。

在启动协程模式中也说明过为什么不用传递Context,因为它有一个默认值EmptyCoroutineContext,需要注意的是这个Context是不可以切换线程的因为它是一个空的上下文对象,如果有这个需求就需要传入具体的Context,例如Dispatchers.IO

//launch
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)
    return coroutine
}
//runBlocking
public actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {
}
//async
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
}

当传入Dispatchers.IO时执行的线程有什么变化呢?

fun main() = runBlocking {
    val user = contextTest()
    logX(user)
}
suspend fun contextTest(): String {
    logX("Start Context")
    withContext(Dispatchers.IO) {
        logX("Loading Context.")
        delay(1000L)
    }
    logX("After Context.")
    return "End Context"
}
fun logX(any: Any?) {
    println(
        """
================================
$any
Thread:${Thread.currentThread().name}
================================
""".trimIndent()
    )
}
//输出结果:
//================================
//Start Context
//Thread:main @coroutine#1
//================================
//================================
//Loading Context.
//Thread:DefaultDispatcher-worker-1 @coroutine#1
//================================
//================================
//After Context.
//Thread:main @coroutine#1
//================================
//================================
//End Context
//Thread:main @coroutine#1
//================================

从输出结果可以得出一个结论:默认是运行在main线程中当传入Dispatchers.IO之后就会进入到IO线程执行,然后在IO线程执行完毕后又回到了main线程,那么除了这两个线程之外是否还有其他线程呢?答案是有,除了这两个之外还有2个:

public actual object Dispatchers {
    /**
     * 用于CPU密集型任务的线程池,一般来说它内部的线程个数是与机器 CPU 核心数量保持一致的
     * 不过它有一个最小限制2,
     */
    public actual val Default: CoroutineDispatcher = DefaultScheduler
    /**
     * 主线程,在Android中才可以使用,主要用于UI的绘制,在普通JVM上无法使用
     */
    public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
    /**
     * 不局限于任何特定线程,会根据运行时的上下文环境决定
     */
    public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
    /**
     * 用于执行IO密集型任务的线程池,它的数量会多一些,默认最大线程数量为64个
     * 具体的线程数量可以通过kotlinx.coroutines.io.parallelism配置
     * 它会和Default共享线程,当Default还有其他空闲线程时是可以被IO线程池复用。
     */
    public val IO: CoroutineDispatcher = DefaultIoScheduler
}

除了上述几个Dispatcher之外还可以自定义Dispatcher

fun main() = runBlocking {
    val user = contextTest()
    logX(user)
}
suspend fun contextTest(): String {
    logX("Start Context")
    //使用自定义的dispatcher
    //				↓
    withContext(myDispatcher) {
        logX("Loading Context.")
        delay(100L)
    }
    logX("After Context.")
    return "End Context"
}
val myDispatcher = Executors.newSingleThreadExecutor {
    Thread(it, "myDispatcher").apply { isDaemon = true }
}.asCoroutineDispatcher()
//输出结果
//================================
//Start Context
//Thread:main @coroutine#1
//================================
//================================
//Loading Context.
//Thread:myDispatcher @coroutine#1
//================================
//================================
//After Context.
//Thread:main @coroutine#1
//================================
//================================
//End Context
//Thread:main @coroutine#1
//================================

通过 asCoroutineDispatcher() 这个扩展函数,创建了一个 Dispatcher。从这里也能看到,Dispatcher 的本质仍然还是线程。那么可以得出一个结论:协程是运行在线程之上的

前面还有一个线程Unconfined,它是一个特殊的线程,没有指定可运行在哪里,但是这个使用时需要谨慎甚至最好不用,通过下面的的代码对比一下:

//不设置执行线程
fun main() = runBlocking {
    logX("Start launch.")
    launch {
        logX("Start Delay launch.")
        delay(1000L)
        logX("End Delay launch.")
    }
    logX("End launch")
}
//输出结果
//================================
//Start launch.
//Thread:main @coroutine#1
//================================
//================================
//End launch
//Thread:main @coroutine#1
//================================
//================================
//Start Delay launch.
//Thread:main @coroutine#2
//================================
//================================
//End Delay launch.
//Thread:main @coroutine#2
//================================
//设置执行线程
fun main() = runBlocking {
    logX("Start launch.")
//				变化在这里
//					↓
    launch(Dispatchers.Unconfined) {
        logX("Start Delay launch.")
        delay(1000L)
        logX("End Delay launch.")
    }
    logX("End launch")
}
//输出结果
//================================
//Start launch.
//Thread:main @coroutine#1
//================================
//================================
//Start Delay launch.
//Thread:main @coroutine#2
//================================
//================================
//End launch
//Thread:main @coroutine#1
//================================
//================================
//End Delay launch.
//Thread:kotlinx.coroutines.DefaultExecutor @coroutine#2
//================================

经过对比可以发现加入Dispatchers.Unconfined会导致代码的运行顺序被修改,这种错误的产生一定会对项目调试造成非常大的影响,而且Dispatchers.Unconfined的定义初衷也不是为了修改代码的执行顺序。

2.万物皆有 Context

在Kotlin协程中,但凡是重要的概念都直接或间接的与CoroutineContext有关系,例如JobDispatcherCoroutineExceptionHandlerCoroutineScope

1.CoroutineScope

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)
    return coroutine
}
/**
 * CoroutineScope的作用域就是把CoroutineContext做了一层封装,核心实现均来自于CoroutineContext
 */
public interface CoroutineScope {
    /**
     *  此作用域的上下文。上下文被作用域封装,并用于实现作为作用域扩展的协程构建器
     */
    public val coroutineContext: CoroutineContext
}

CoroutineScope的源码注释写的很清楚,核心实现在于CoroutineContextCoroutineScope只是做了封装而已,然后就可以批量的控制协程了,例如下面的代码实现:

fun main() = runBlocking {
    val scope = CoroutineScope(Job())
    scope.launch {
        logX("launch 1")
    }
    scope.launch {
        logX("launch 2")
    }
    scope.launch {
        logX("launch 3")
    }
    scope.launch {
        logX("launch 4")
    }
    delay(500L)
    scope.cancel()
    delay(1000L)
}

2.Job

//Job#Job
public interface Job : CoroutineContext.Element {
}
//CoroutineContext#Element
public interface CoroutineContext {
    /**
     * 从该上下文返回具有给定键的元素,或返回null
     */
    public operator fun <E : Element> get(key: Key<E>): E?
    /**
     * 从初始值开始累积此上下文的条目,并从左到右对当前累加器值和此上下文的每个元素应用操作。
     */
    public fun <R> fold(initial: R, operation: (R, Element) -> R): R
    /**
     * 返回包含该上下文和其他上下文元素的上下文。
     * 删除这个上下文中与另一个上下文中具有相同键的元素。
     */
    public operator fun plus(context: CoroutineContext): CoroutineContext {}
    /**
     * 返回包含此上下文中的元素的上下文,但不包含具有指定键的元素。
     */
    public fun minusKey(key: Key<*>): CoroutineContext
    /**
     * CoroutineContext元素的键
     */
    public interface Key<E : Element>
    /**
     * CoroutineContext的一个元素。协程上下文的一个元素本身就是一个单例上下文。
     */
    public interface Element : CoroutineContext {
    }
}

Job实现了CoroutineContext.Element,CoroutineContext.Element又实现了CoroutineContext那么就可以认为Job间接实现了CoroutineContext,所以可以认定Job就是一个CoroutineContext

所以在定义Job时下面两种定义方式都可以:

val job: CoroutineContext = Job()
val job: Job = Job()

3.Dispatcher

public actual object Dispatchers {
    public actual val Default: CoroutineDispatcher = DefaultScheduler
    public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
    public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
    public val IO: CoroutineDispatcher = DefaultIoScheduler
    public fun shutdown() { }
}
public abstract class CoroutineDispatcher :
    AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {}
public interface ContinuationInterceptor : CoroutineContext.Element {

Dispatcher中的每一个线程继承自CoroutineDispatcherCoroutineDispatcher实现了ContinuationInterceptor接口,ContinuationInterceptor又实现了CoroutineContext接口,由此就可以知道DispatcherCoroutineContext是如何产生关联的了,或者说Dispatcher就是CortinueContext

4.CoroutineExceptionHandler

/**
 * 协程上下文中一个可选的元素,用于处理未捕获的异常
 */
public interface CoroutineExceptionHandler : CoroutineContext.Element {
    /**
     *
     */
    public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>
    /**
     * 处理给定上下文中未捕获的异常。如果协程有未捕获的异常,则调用它。
     */
    public fun handleException(context: CoroutineContext, exception: Throwable)
}

CoroutineExceptionHandler主要用来处理协程中未捕获的异常,未捕获的异常只能来自根协程,子协程未捕获的异常会委托给它们的父协程,父协程也委托给父协程,以此类推,直到根协程。所以安装在它们上下文中的CoroutineExceptionHandler永远不会被使用。

以上就是Kotlin协程Context应用使用示例详解的详细内容,更多关于Kotlin协程Context应用的资料请关注我们其它相关文章!

(0)

相关推荐

  • Room Kotlin API的使用入门教程

    Room 是 SQLite 的封装,它使 Android 对数据库的操作变得非常简单,也是迄今为止我最喜欢的 Jetpack 库.在本文中我会告诉大家如何使用并且测试 Room Kotlin API,同时在介绍过程中,我也会为大家分享其工作原理. 我们将基于 Room with a view codelab 为大家讲解.这里我们会创建一个存储在数据库的词汇表,然后将它们显示到屏幕上,同时用户还可以向列表中添加单词. 定义数据库表 在我们的数据库中仅有一个表,就是保存词汇的表.Word 类代表表中

  • Kotlin协程的线程调度示例详解

    目录 引言 一.协程的分发器作用 1.1 测试代码 1.2 CoroutineScope.launch 1.2.1 newCoroutineContext 1.3 startCoroutineCancellable 1.3.1 intercepted() 1.3.2 CoroutineDispatcher 1.3.3 小结 1.4 DispatchedContinuation 1.5 DefaultScheduler 1.5.1 SchedulerCoroutineDispatcher 1.5.

  • Kotlin协程低级api startCoroutine与ContinuationInterceptor

    目录 聊一聊kotlin协程“低级”api startCoroutine ContinuationInterceptor 实战 kotlin协程api中的 async await 通过startCoroutine与ContinuationInterceptor实现自定义的 async await 本章代码 最后 聊一聊kotlin协程“低级”api Kotlin协程已经出来很久了,相信大家都有不同程度的用上了,由于最近处理的需求有遇到协程相关,因此今天来聊一Kotlin协程的“低级”api,首先

  • Kotlin启动协程的三种方式示例详解

    目录 1.launch启动协程 2.runBlocking启动协程 3.async启动协程 1.launch启动协程 fun main() = runBlocking { launch { delay(1000L) println("World!") } println("Hello") } fun main() { GlobalScope.launch { delay(1000L) println("World!") } println(&qu

  • Android使用Kotlin API实践WorkManager

    WorkManager 提供了一系列 API 可以更加便捷地规划异步任务,即使在应用被关闭之后或者设备重启之后,仍然需要保证立即执行的或者推迟执行的任务被正常处理.对于 Kotlin 开发者,WorkManager 为协程提供了最佳的支持.在本文中,我将通过实践 WorkManager codelab 为大家展示 WorkManager 中与协程相关的基本操作.那么让我们开始吧! WorkManager 基础 当您需要某个任务保持运行状态,即使用户切换到别的界面或者用户将应用切换到后台,甚至设备

  • Kotlin协程launch启动流程原理详解

    目录 1.launch启动流程 反编译后的Java代码 2.协程是如何被启动的 1.launch启动流程 已知协程的启动方式之一是Globalscope.launch,那么Globalscope.launch的流程是怎样的呢,直接进入launch的源码开始看起. fun main() { coroutineTest() Thread.sleep(2000L) } val block = suspend { println("Hello") delay(1000L) println(&q

  • kotlin之协程的理解与使用详解

    前言         为什么在kotlin要使用协程呢,这好比去了重庆不吃火锅一样的道理.协程的概念并不陌生,在python也有提及.任何事务的作用大多是对于所依赖的环境相应而生的,协程对于kotlin这门语言也不例外.协程的优点,总的来说有如下几点:轻量级,占用更少的系统资源: 更高的执行效率: 挂起函数较于实现Runnable或Callable接口更加方便可控: kotlin.coroutine 核心库的支持,让编写异步代码更加简单.当然在一些不适应它的用法下以上优势也会成为劣势. 1.协程

  • Python 异步协程函数原理及实例详解

    这篇文章主要介绍了Python 异步协程函数原理及实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一. asyncio 1.python3.4开始引入标准库之中,内置对异步io的支持 2.asyncio本身是一个消息循环 3.步骤: (1)创建消息循环 (2)把协程导入 (3)关闭 4.举例: import threading # 引入异步io包 import asyncio # 使用协程 @ asyncio.coroutine def

  • 关于Python核心框架tornado的异步协程的2种方法详解

    什么是异步? 含义 :双方不需要共同的时钟,也就是接收方不知道发送方什么时候发送,所以在发送的信息中就要有提示接收方开始接收的信息,如开始位,同时在结束时有停止位 现象:没有共同的时钟,不考虑顺序来了就处理 直观感受:就是不用等了,效率高 同步 含义:指两个或两个以上随时间变化的量在变化过程中保持一定的相对关系 现象:有一个共同的时钟,按来的顺序一个一个处理 直观感受 :就是需要等候,效率低下 那么今天我们看怎么用2种方法用代码实现tornado的异步? 这些是导入的包: 2种方法用代码实现to

  • Kotlin Coroutines执行异步加载示例详解

    前言 Kotlin Coroutines是Kotlin推出的新的异步API.并不是解决所有问题的最优方案,但是希望在许多情况下它会使事情变得更容易一些.这里只简单的展示一下这个库在安卓中的具体使用方案.下面话不多说了,来一起看看详细的介绍吧. 引入Coroutines //在application的build.gradle文件中的android节点添加如下的代码 kotlin { experimental { coroutines 'enable' } } //添加下面两行到依赖中 implem

  • Python协程的用法和例子详解

    从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数.可是,在协程中, yield 通常出现在表达式的右边(例如, datum = yield),可以产出值,也可以不产出 -- 如果 yield 关键字后面没有表达式,那么生成器产出 None. 协程可能会从调用方接收数据,不过调用方把数据提供给协程使用的是 .send(datum) 方法,而不是next(-) 函数. ==yield 关键字甚至还可以不接收或传出数据.不管数据如何流动, yield 都是一种流程控制工具,使用

  • Swoole4.4协程抢占式调度器详解

    前言 Swoole内核团队开设的专栏,会逐渐投入精力写文章介绍Swoole的开发历程,实现原理,应用实践等,大家可以更好的交流,共同学习,建设PHP生态. 协程调度 去年Swoole推出了4.0版本后,完整的支持PHP协程,我们可以基于协程实现CSP编程,身边的开发者惊呼,原来PHP代码还可以这样写.Swoole的协程默认是基于IO调度,程序中有阻塞会自动让出当前协程,协程的各种优势我们不在这里展开讨论.如果是IO密集型的场景,可以表现得很不错.但是对于CPU密集型的场景,会导致一些协程因为得不

  • kotlin和Java的相互调用示例详解

    前言 互操作就是在Kotlin中可以调用其他编程语言的接口,只要它们开放了接口,Kotlin就可以调用其成员属性和成员方法,这是其他编程语言所无法比拟的.同时,在进行Java编程时也可以调用Kotlin中的API接口. 1.在kotlin中调用Java方法 Kotlin和Java是两种不同的语言,所以在互相调用的时候,会有一些特殊的语法.kotlin中对象属性默认就带有setter和getter方法,所以在kotlin中调用Java时直接变量名点属性就可获取到属性的setter和getter的一

  • Kotlin内存陷阱inline使用技巧示例详解

    目录 引言 错误示例 推荐示例 小结 总结 引言 inline ,翻译过来为 内联 ,在 Kotlin 中,一般建议用于 高阶函数 中,目的是用来弥补其运行时的 额外开销. 其原理也比较简单,在调用时将我们的代码移动到调用处使用,从而降低方法调用时的 栈帧 层级. 栈帧: 指的是虚拟机在进行方法调用和方法执行时的数据结构,每一个栈帧里都包含了相应的数据,比如 局部参数,操作数栈等等. Jvm在执行方法时,每执行一个方法会产生一个栈帧,随后将其保存到我们当前线程所对应的栈里,方法执行完毕时再将此方

  • Kotlin中的5种单例模式示例详解

    前言 最近在学习Kotlin这门语言,在项目开发中,运用到了单例模式.因为其表达方式与Java是不同的.所以对不同单例模式的实现进行了分别探讨.主要单例模式实现如下: 饿汉式 懒汉式 线程安全的懒汉式 双重校验锁式 静态内部类式 PS:该篇文章不讨论单例模式的运用场景与各种模式下的单例模式的优缺点.只讨论在Java下不同单例模式下的对应Kotlin实现. 一.饿汉式实现 //Java实现 public class SingletonDemo { private static SingletonD

随机推荐