IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Kotlin协程的Dispatchers和CoroutineContext(七) -> 正文阅读

[移动开发]Kotlin协程的Dispatchers和CoroutineContext(七)

一、前言

? 协程总是在由CoroutineContext类型表示的某个上下文中执行。它是由一组元素构成的。主要是JobDispatchers构成的

二、Dispatchers

? Dispatchers来确定协程由哪个或者哪些线程来执行。也可以使协程不受限制的运行。以下是参考例子

@Test
fun dispather(){
    runBlocking {
        launch { // 父级主线程
            println("main runBlocking      : I'm working in thread ${Thread.currentThread().name}")
        }
        launch(Dispatchers.Unconfined) { // 不受限制,也运行在主线程
            println("Unconfined            : I'm working in thread ${Thread.currentThread().name}")
        }
        launch(Dispatchers.Default) { // 默认使用共享线程池
            println("Default               : I'm working in thread ${Thread.currentThread().name}")
        }
        launch(newSingleThreadContext("MyOwnThread")) { // 运行在自定义的线程池里面
            println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
        }
    }
}

运行结果如下:

Unconfined            : I'm working in thread main
Default               : I'm working in thread DefaultDispatcher-worker-1
newSingleThreadContext: I'm working in thread MyOwnThread
main runBlocking      : I'm working in thread main

launch { ... }不带参数使用时,它从启动它的CoroutineScope继承上下文(以及调度程序)。在这种情况下,它继承了runBlockingmain线程中运行的主协程的上下文。

Dispatchers.Unconfined是一个特殊的调度器,它看起来也运行在main线程中,但实际上它是一种不同的机制,稍后解释。

在范围中没有明确指定其他调度程序时使用的默认调度程序。它由Dispatchers.Default表示,并使用共享的后台线程池。

newSingleThreadContext创建一个线程供协程运行。专用线程是一种非常昂贵的资源。在实际应用程序中,它必须在不再需要时使用close函数释放,或者存储在顶级变量中并在整个应用程序中重用。

三、Dispatchers.Unconfined

这里看下不受限和受限的协程运行情况

@Test
fun unconfined(){
  runBlocking {
      launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
          println("Unconfined      : I'm working in thread ${Thread.currentThread().name}")
          delay(500)
          println("Unconfined      : After delay in thread ${Thread.currentThread().name}")
      }
      launch { // context of the parent, main runBlocking coroutine
          println("main runBlocking: I'm working in thread ${Thread.currentThread().name}")
          delay(1000)
          println("main runBlocking: After delay in thread ${Thread.currentThread().name}")
      }
  }  
}

运行结果如下:

Unconfined      : I'm working in thread main
main runBlocking: I'm working in thread main
Unconfined      : After delay in thread kotlinx.coroutines.DefaultExecutor
main runBlocking: After delay in thread main

可以看出来受限的一直是同一个线程挂起和恢复,而不受限的则是由main线程挂起,默认线程池恢复。所以不受限的协程不由一个线程控制。但是具体使用场景暂时不太清楚。官方推荐的场景如下:

unconfined dispatcher 是一种高级机制,在某些特殊情况下很有帮助,在这种情况下,不需要为稍后执行的协程分派或产生不良副作用,因为协程中的某些操作必须立即执行。unconfined dispatcher 不应该用在通用代码中。

四、线程之间的跳转

协程可以在不同线程之间进行切换

@OptIn(kotlinx.coroutines.ObsoleteCoroutinesApi::class)
@Test
fun switchThread(){
    runBlocking {
        newSingleThreadContext("Ctx1").use { ctx1 ->
            newSingleThreadContext("Ctx2").use { ctx2 ->
                runBlocking(ctx1) {
                    println("Started in ctx1")
                    withContext(ctx2) {
                        println("Working in ctx2")
                    }
                    println("Back to ctx1")
                }
            }
        }
    }
}

需要注意的是该方式在将来可能会被替换掉,当使用线程时候需要注意线程释放的问题,线程池的释放可以使用ExecutorCoroutineDispatcher.close()函数

五、协程及其子协程

一般来说在协程之上创建的协程受该协程管理,比如在A协程上创建B协程,那么A协程取消后,B协程也会取消。可以使用以下两种方式对其进行修改

  • 启动协程时候指定不同的范围,如下所示

    @Test
    fun childJob(){
        runBlocking {
            // launch a coroutine to process some kind of incoming request
            val request = launch {
                // it spawns two other jobs
                launch(Job()) {
                    println("job1: I run in my own Job and execute independently!")
                    delay(1000)
                    println("job1: I am not affected by cancellation of the request")
                }
                // and the other inherits the parent context
                launch {
                    delay(100)
                    println("job2: I am a child of the request coroutine")
                    delay(1000)
                    println("job2: I will not execute this line if my parent request is cancelled")
                }
            }
            delay(500)
            request.cancel() // cancel processing of the request
            delay(1000) // delay a second to see what happens
            println("main: Who has survived request cancellation?")
        }
    }
    

    可以看到以下结果

    job1: I run in my own Job and execute independently!
    job2: I am a child of the request coroutine
    job1: I am not affected by cancellation of the request
    main: Who has survived request cancellation?
    

    当延迟500ms后取消协程,Job1依然输出了延迟后的内容,Job2却没有。所以可以看出Job1不受父协程控制

  • 如果在启动协程时显式指定了不同的范围(例如,GlobalScope.launch,或者自己定义的协程范围在另外一个协程中启动),则它不会Job从父范围继承 。

六、家长责任

一般来说,父协程取消后,子协程就会跟着取消。但是一般来说,如果父协程不主动取消的话,会等子协程全部执行完毕再取消。除非,我们要等到协程执行完获取数据再进行下一步操作。否则不要使用Job.join()

七、协程的名字

出于调试的目的,可能会需要对协程进行命名,可以使用CoroutineName来进行处理

 fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")

@Test
fun coroutineName(){
    runBlocking {
        println("Started main coroutine")
        // run two background value computations
        val v1 = async(CoroutineName("v1coroutine")) {
            delay(500)
            println("Computing v1")
            252
        }
        val v2 = async(CoroutineName("v2coroutine")) {
            delay(1000)
            println("Computing v2")
            6
        }
        println("The answer for v1 / v2 = ${v1.await() / v2.await()}")
    }
}

运行结果如下:

[Test worker @coroutine#1] Started main coroutine
[Test worker @v1coroutine#2] Computing v1
[Test worker @v2coroutine#3] Computing v2
[Test worker @coroutine#1] The answer for v1 / v2 = 42

八、元素组合

之前提过CoroutineContext可以由多个元素构成的,所以我们在实际传入该元素时候可以由多个元素进行组合,组合方式使用+。如下

launch(Dispatchers.Default + CoroutineName("test")) {
    println("I'm working in thread ${Thread.currentThread().name}")
}

结果如下

I'm working in thread DefaultDispatcher-worker-1 @test#2

九、协程范围

没有协程都有自己的范围,一般在定义时候就会自动生成范围CoroutineScope。有时候我们需要主动去控制协程范围,比如和页面生命周期绑定。在页面销毁的时候也把协程销毁掉。CoroutineScope实例可以由创建CoroutineScope()MainScope()工厂函数。前者创建一个通用范围,而后者创建一个 UI 应用程序范围并使用Dispatchers.Main作为默认调度程序:

这里我们使用MainScope来进行演示

class Activity {
    private val mainScope = MainScope()

    fun destroy() {
        mainScope.cancel()
    }
    // to be continued ...
  
 // class Activity continues
    fun doSomething() {
        // launch ten coroutines for a demo, each working for a different time
        repeat(10) { i ->
            mainScope.launch {
                delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
                println("Coroutine $i is done")
            }
        }
    }
	} // class Activity ends 
}

使用情况如下:

val activity = Activity()
activity.doSomething() // run test function
println("Launched coroutines")
delay(500L) // delay for half a second
println("Destroying activity!")
activity.destroy() // cancels all coroutines
delay(1000) // visually confirm that they don't work

不过实际上Android对这个有专门的支持。不需要这么麻烦

十、读取本地数据

本段实际上并不常用。不过作为学习记录一下

具体参考以下会更好一点

https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-context-element.html

threadLocal.set("main")
println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
val job = launch(Dispatchers.Default + threadLocal.asContextElement(value = "launch")) {
    println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
    yield()
    println("After yield, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
}
job.join()
println("Post-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")

输出结果如下

Pre-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main'
Launch start, current thread: Thread[DefaultDispatcher-worker-1 @coroutine#2,5,main], thread local value: 'launch'
After yield, current thread: Thread[DefaultDispatcher-worker-2 @coroutine#2,5,main], thread local value: 'launch'
Post-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main'

讲下这个意思,就是说在线程中存入一个值的话,可以在不同的协程之间获取或者修改,但是要注意的是如果用协程改变值后,需要使用协程把值取出。而且很容易忘记设置相应的上下文元素。如果运行协程的线程不同,那么从协程访问的线程局部变量可能会有意外的值。为了避免这种情况,建议使用ensurePresent方法并在不正确的使用中快速失败。

十一、参考文档

  1. 协程的Dispatchers和CoroutineContext

    https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html

  2. Kotlin Cheatsheet: Coroutine Dispatchers

    https://medium.com/dont-code-me-on-that/kotlin-cheatsheet-coroutine-dispatchers-1dc80f08bdd6

  3. asContextElement

    https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-context-element.html

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-03 11:19:29  更:2021-08-03 11:21:04 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/6 2:54:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码