Kotlin协程——CoroutineContext协程上下文

一、认识CoroutineContext

看一下CoroutineContext的源码,如下:

@SinceKotlin("1.3")
public interface CoroutineContext {
  
    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

    public interface Key<E : Element>

    //Element接口实现CoroutineContext
    public interface Element : CoroutineContext {
      //...代码略...
    }
}

CoroutineContext是一个接口,它的实现类常见的有:DefaultIoSchedulerCoroutineDispatcherEmptyCoroutineContextMainCoroutineDispatcher等等。CoroutineContext的接口设计,就跟集合 API 一样。准确来说,它的 API 设计和 Map 十分类似。

二、Dispatcher

我们用launch启动协程时,第一个参数就是CoroutineContext,如果不传的话默认就是EmptyCoroutineContext,顾名思义,这就是一个空的上下文对象。而如果我们想要指定 launch 工作的_线程池_的话,就需要自己传 context 这个参数了,我们可以传入一个 Dispatcher 对象作为参数,比如下面的例子:

fun main() {
    printMsg("start") 
   GlobalScope.launch(context = Dispatchers.IO) {  //Dispatchers指定为IO线程(日志中复用了Dispatchers.default的线程)
       printMsg("job start")
       delay(500)
       printMsg("job end")
   }
    Thread.sleep(1000)
    printMsg("end")
}

//打印日志
2023-09-27T16:45:08.888464 main start
2023-09-27T16:45:08.964462500 DefaultDispatcher-worker-1 @coroutine#1 job start   //Dispatchers.IO复用了Dispatchers.Default
2023-09-27T16:45:09.481660100 DefaultDispatcher-worker-1 @coroutine#1 job end     //Dispatchers.IO复用了Dispatchers.Default
2023-09-27T16:45:09.962842700 main end

官方除了提供了 Dispatchers.IO 以外,还提供了 Dispatchers.MainDispatchers.UnconfinedDispatchers.Default 这几种内置 Dispatcher。下面介绍一 下:

  • Dispatchers.Main 它只在 UI 编程平台才有意义,在 Android、Swing 之类的平台上,一般只有 Main 线程才能用于 UI 绘制。这个 Dispatcher 在普通的 JVM 工程当中,是无法直接使用的。

  • Dispatchers.Unconfined 无拘束的,当前协程可能运行在任意线程之上(因为线程不可控,生产环境不要用)。

  • Dispatchers.Default 它是用于 CPU 密集型任务的线程池。一般来说,它内部的线程个数是与机器 CPU 核心数量保持一致的,不过它有一个最小限制2。

  • Dispatchers.IO 它是用于 IO 密集型任务的线程池。它内部的线程数量一般会更多一些(比如 64 个),具体线程的数量我们可以通过参数来配置:kotlinx.coroutines.io.parallelism

需要特别注意的是,Dispatchers.IO 底层是可能复用 Dispatchers.Default 当中的线程的,比如上面的例子。

我们也可以自定义Dispatchers,如下:

fun main() {
    //自定义Dispatcher
    val myDispatcher:ExecutorCoroutineDispatcher = Executors.newSingleThreadExecutor{
        Thread(it,"MyDispatcher ").apply { isDaemon = true }
    }.asCoroutineDispatcher()   //扩展函数

    //asCoroutineDispatcher()扩展函数的源码
    public fun ExecutorService.asCoroutineDispatcher(): ExecutorCoroutineDispatcher =
        ExecutorCoroutineDispatcherImpl(this)

   GlobalScope.launch(myDispatcher) {  //变化在这里:用自己的Dispatcher
     //...
   }
   
   //日志:
   2023-09-27T17:05:54.307281600 MyDispatcher  @coroutine#1 job start  //线程名:MyDispatcher
}

从这里我们也能看到,Dispatcher 的本质仍然还是线程。这也再次验证了我们之前的说法:协程运行在线程之上

我们看看DispatchersCoroutineContext的代码关系:

image.png

三、万物皆有 Context

在 Kotlin 协程当中,但凡是重要的概念,都或多或少跟 CoroutineContext 有关系:JobDispatcherCoroutineExceptionHandlerCoroutineScope,甚至挂起函数,它们都跟CoroutineContext 有着密切的联系。甚至,它们之中的 JobDispatcherCoroutineExceptionHandler 本身,就是 Context

1、CoroutineScope

如果要调用 launch,就必须先有_协程作用域_,也就是CoroutineScope。看launchCoroutineScope的源码:

public fun CoroutineScope.launch(             //CoroutineScope协程作用域扩展函数
    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
}

public interface CoroutineScope {
    public val coroutineContext: CoroutineContext  //协程上下文
}

CoroutineScope 的源码会发现,它其实就是一个简单的接口,而这个接口只有唯一的成员,就是 CoroutineContext。所以,CoroutineScope 只是对 CoroutineContext 做了一层封装而已,它的核心能力其实都来自于 CoroutineContext

CoroutineScope 最大的作用,就是可以方便我们批量控制协程:

fun main() {
    runBlocking {
        val scope = CoroutineScope(Job())
        printMsg("scope start")
        val job1 = scope.launch {
            printMsg("job1 start")
            delay(300)   //job1的delay
            printMsg("job1 end")
        }

        val job2 = scope.launch {
            printMsg("job2 start")
            delay(700)  //job2的delay
            printMsg("job2 end")
        }

        val job3 = scope.launch {
            printMsg("job3 start")
            delay(1500)    //job3的delay
            printMsg("job3 end")
        }
        delay(500)   //第一次delay,大于job1的delay时间
        //取消scope,就批量取消了job1,job2,job3
        scope.cancel()
        delay(2000)
        printMsg("scope end")
    }
}

//打印日志  job2 end 和 job3 end 没有输出
2023-09-28T14:08:28.543547600 main @coroutine#1 scope start
2023-09-28T14:08:28.553548600 DefaultDispatcher-worker-1 @coroutine#2 job1 start
2023-09-28T14:08:28.558545800 DefaultDispatcher-worker-2 @coroutine#3 job2 start
2023-09-28T14:08:28.560548900 DefaultDispatcher-worker-3 @coroutine#4 job3 start
2023-09-28T14:08:28.864393 DefaultDispatcher-worker-2 @coroutine#2 job1 end
2023-09-28T14:08:31.085238900 main @coroutine#1 scope end

2、Job

如果说 CoroutineScope 是封装了 CoroutineContext,那么 Job 就是一个真正的CoroutineContext 了。源码如下:

public interface Job : CoroutineContext.Element {}

上面这段代码,Job 继承自 CoroutineContext.Element,而CoroutineContext.Element 仍然继承自CoroutineContext,这就意味着 Job 是间接继承自CoroutineContext 的。所以说,Job 确实是一个真正的 CoroutineContext

3、CoroutineName

CoroutineName,当我们创建协程的时候,可以传入指定的名称,比如:

fun main() {
    runBlocking {
        launch(context = CoroutineName("MyFirstCoroutine")) {  //指定协程的名字
            printMsg("launch")
        }
    }
}

//打印日志
2023-09-28T14:46:16.505510500 main @MyFirstCoroutine#2 launch

看下CoroutineName源码:

public data class CoroutineName(
    val name: String
) : AbstractCoroutineContextElement(CoroutineName) {
    
    public companion object Key : CoroutineContext.Key<CoroutineName>

    override fun toString(): String = "CoroutineName($name)"
}

public abstract class AbstractCoroutineContextElement(public override val key: Key<*>) : Element{}   //CoroutineContext.Element

CoroutineName也是继承了CoroutineContext

4、CoroutineExceptionHandler

CoroutineContext 当中,还有一个重要成员是 CoroutineExceptionHandler,它主要负责处理协程当中的异常。源码如下:

public interface CoroutineExceptionHandler : kotlin.coroutines.CoroutineContext.Element {
    public companion object Key : kotlin.coroutines.CoroutineContext.Key<kotlinx.coroutines.CoroutineExceptionHandler> {
    }

    public abstract fun handleException(context: kotlin.coroutines.CoroutineContext, exception: kotlin.Throwable): kotlin.Unit
}

CoroutineExceptionHandler 真正重要的,其实只有 handleException() 这个方法,如果我们要自定义异常处理器,我们就只需要实现该方法即可。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //打印协程名
        System.setProperty("kotlinx.coroutines.debug", "on")

        val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
            //这里输出日志
            log("exception---> $throwable")
        }

        lifecycleScope.launch(context = exceptionHandler) {
            val i = 2 / 0   //这里抛出异常
        }
    }

    //打印日志的方法
    fun log(msg: String) {
        Log.d("TAG", "${Thread.currentThread().name} $msg")
    }
}

//打印日志
main @coroutine#2 exception---> java.lang.ArithmeticException: divide by zero

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

图片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值