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协程的前世今生 -> 正文阅读

[移动开发]Kotlin协程的前世今生

协程中有几个概念: CoroutineScope, Job,CoroutineContext

CoroutineScope 协程作用域

异步作用域函数

创建作用域有两种创建方式,常用的launch只是CoroutineScope的扩展函数

//没有返回结果
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job
//有返回结果
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T>

其中有个概念结构化并发,无论你再任何页面启动协程,并控制其生命周期,都应该创建 CoroutineScop。 在顶层协程代码块中可以创建子Coroutine执行,当所有的子协程执行完毕并且返回的时候,代码块才执行完毕。

在Android中引入KTX类,已经未特定的生命周期提供了CoroutineScope, 如viewModelScopelifecycleScope

val job = GlobalScope.launch { // 启动一个新协程并保持对这个作业的引用
    delay(1000L)
    println("World!")
}
println("Hello,")
job.join() // 等待直到子协程执行结束

fun main() = runBlocking<Unit> { // 开始执行主协程
    GlobalScope.launch { // 在后台启动一个新的协程并继续
        delay(1000L)
        println("World!")
    }
    println("Hello,") // 主协程在这里会立即执行
    delay(2000L)      // 延迟 2 秒来保证 JVM 存活
}

runBlockingcoroutineScope类似,都是等待协程体以及子协程结束。区别在于runBlocking方法会阻塞当前线程等待(常规函数),而coroutineScope只是挂起,会释放底层线程用于其他(挂起函数)。

CoroutineContext 协程上下文

协程上下文是各种不同元素的集合,其中主要元素是协程中的Job。

而定义协程上下文的元素,有下面几个部分:

  • Job,管理协程的生命周期
  • CoroutineDispatcher,分发任务到合适的线程
  • CoroutineName,协程的名称,用于调试
  • CoroutineExceptionHandler,处理未捕获的异常

其中一些元素具有默认值:CoroutineDispatcher 的默认值是 Dispatchers.DefaultCoroutineName的默认值是 coroutine

val scope = CoroutineScope(Job() + Dispatchers.Main)
 
val job = scope.launch {
    // 这里的新协程的父亲是 scope
    val result = async {
        // 这里的新协程的父亲是上面的 scope.launch 启动的协程
    }.await()
}

父协程的职责:一个父协程总是等待所有的子协程执行结束。

在协程的继承结构中,每一个协程都会有一个父协程,子协程创建的时候继承的 CoroutineContext 是父亲的 CoroutineContext,传递到协程构建器的参数优先于继承上下文的参数
在这里插入图片描述
最终的CoroutineContext 的协程调度器是 Dispatchers.IO,因为它被协程构建器中的参数覆盖了。

Job

job代表了一个协程。可以对协程进行join()cancel(),管理协程的生命周期

  • Join():等待执行子线程执行结束,阻塞当前线程
  • canenl():取消协程的执行
  • cancelAndJoin():它合并了对 cancel 以及 join 的调用

所有 kotlinx.coroutines 中的挂起函数都是 可被取消的 。它们检查协程的取消, 并在取消时抛出 CancellationException,如果协程正在执行计算任务,并且没有检查取消的话,那么它是不能被取消的。

组合上下文中的元素

如果在一个协程中定义多个元素,可以用+操作符来实现。由于 CoroutineContext 包含一系列元素,当创建新的 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
//SupervisorJob ,它会改变协程作用域的异常处理
val a = CoroutineScope(SupervisorJob() + coroutineContext).launch(handler) {
  delay(1000)
  System.err.println("(Main.kt:51)    ${Thread.currentThread()}")
}

调度器

用来指定协程代码块在哪个线程中执行。kotlin提供了几个默认的协程调度器,分别是DefaultMainUnconfined

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

协程取消和超时

协程体如果已经执行实际上属于不可取消的, 在协程体中通过检查job.isActive或者ensureActive判断协程是否处于活跃中,通过取消函数的参数指定异常CancellationException可以自定义异常对象

fun Job.ensureActive(): Unit {
    if (!isActive) {
         throw getCancellationException()
    }
}
while (i < 5) {
    ensureActive()}

使用yield()取消协程

yield 会进行的第一个工作就是检查任务是否完成,如果 Job 已经完成的话,就会抛出 CancellationException 来结束协程。yield 应该在定时检查中最先被调用,就像前面提到的 ensureActive 一样。

Deferred 是一种 Job,它也是可以被取消的。对已经被取消的 deferred 调动 await 方法会抛出 JobCancellationException

val deferred = async {}
 
deferred.cancel()
val result = deferred.await() // throws JobCancellationException!

协程的取消需要代码配合实现,所以确保你在代码中检测了取消,以避免额外的无用工作。

运行不能取消的代码块

当你需要挂起一个被取消的协程,你可以将相应的代码包装在 withContext(NonCancellable) {……} 中,并使用 函数以withContextNonCancellable上下文。

val job = launch {
    try {
        repeat(1000) { i ->
            println("job: I'm sleeping $i ...")
            delay(500L)
        }
    } finally {
        withContext(NonCancellable) {
            println("job: I'm running finally")
            delay(1000L)
            println("job: And I've just delayed for 1 sec because I'm non-cancellable")
        }
    }
}
delay(1300L) // 延迟一段时间
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消该作业并等待它结束
println("main: Now I can quit.")


job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
job: I'm running finally
job: And I've just delayed for 1 sec because I'm non-cancellable
main: Now I can quit.

超时

在实践中绝大多数取消一个协程的理由是它有可能超时。

//withContext
 //引用并启动了一个单独的协程在延迟后取消追踪 会抛出TimeoutCancellationException
fun main() = runBlocking {
    try {
        withTimeout(2000L) {
            repeat(100_000) {
                println("launch_____$it")
                delay(1000L)
            }
        }
    } catch (error: TimeoutCancellationException) {
        println("error:$error")
    }
    println("Game is over")
}
//withTimeoutOrNull 通过返回 null 来进行超时操作,从而替代抛出一个异常:
fun main() = runBlocking {
    try {
        val result = withTimeoutOrNull(2000L) {
            repeat(100_000) {
                println("launch_____$it")
                delay(1000L)
            }
            "Done"
        }
        println("result____$result")
    } catch (error: TimeoutCancellationException) {
        println("error:$error")
    }
    println("Game is over")
}

协程异常

supervisorjob,子线程的失败不会影响其他的子协程,此外,SupervisorJob 也不会传播异常,而是让子协程自己处理。

val scope = CoroutineScope(SupervisorJob())
scope.launch {
    // Child 1
}
scope.launch {
    // Child 2
}
val scope = CoroutineScope(Job())
scope.launch {
    supervisorScope {
        launch {
            // Child 1
        }
        launch {
            // Child 2
        }
    }
}

这两种情况下,child#1 失败了,scopechild#2 都不会被取消。

SupervisorJob 仅在属于下面两种作用域时才起作用:使用 supervisorScope 或者 CoroutineScope(SupervisorJob()) 创建的作用域。

如果SupervisorJob 是父协程通过 scope.launch 创建的,SupervisorJob 是不会发挥任何作用。

在我们处理异常的时候,通常是调用try/catch捕获异常,针对下面情况:

supervisorScope {
    val deferred = async {
        codeThatCanThrowExceptions()
    }
    try {
        deferred.await()
    } catch(e: Exception) {
        // SupervisorJob 让协程自己处理异常,可以捕获到异常
    }
}

coroutineScope {
    try {
        val deferred = async {
            codeThatCanThrowExceptions()
        }
        deferred.await()
    } catch(e: Exception) {
        // 由其他协程创建的协程如果发生了异常,也将会自动传播到父协程,无论你的协程构建器是什么
        //所以没有捕获到
    }
}

CoroutineExceptionHandler

协程异常处理器 CoroutineExceptionHandler 是 CoroutineContext 中的一个可选元素,它可以帮助你 处理未捕获异常

val handler = CoroutineExceptionHandler {
    context, exception -> println("Caught $exception")
}

//handel处理要放到父协程上才能捕获异常
val scope = CoroutineScope(Job())
scope.launch(handler) {
    launch {
        throw Exception("Failed coroutine")
    }
}

//supervisorScope 由子协程处理异常
supervisorScope {
        val child = launch(handler) {
            println("The child throws an exception")
            throw AssertionError()
        }
        println("The scope is completing")
    }

捕获条件:

  • 是被可以自动抛异常的协程抛出的(launch,而不是 async
  • 在 CoroutineScope 或者根协程的协程上下文中(CoroutineScope 的直接子协程或者 supervisorScope

参考

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-23 16:48:38  更:2021-08-23 16:49:08 
 
开发: 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年11日历 -2024/11/23 9:36:37-

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