初学协程时整理的3000字笔记

简介: 初学协程时整理的3000字笔记

协程


定义

官方描述:协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。


协程与线程的区别

协程是编译器级别的,线程是系统级别的


优势

协程就像轻量级的线程,线程是由系统调度的,线程的阻塞和切换开销都很大。而协程依赖于线程,协程挂起和切换的时候不需要阻塞线程,几乎是无代价的, 一个线程里面可以创建任意多个协程


使用

runBlocking

private fun test1() {
    runBlocking {
        Log.e(TAG, "test1: 进入协程,准备延迟")
        delay(1000)
        Log.e(TAG, "test1: 延迟结束")
    }
    Log.e(TAG, "test1: 主线程")
}
复制代码

runBlocking启动的协程任务会阻断当前线程,直到该协程执行结束。当协程执行结束之后,页面才会被显示出来。


GlobalScope.launch

调用方法如下:

private fun test2() {
    GlobalScope.launch{
        log("进入协程")
        delay(1000)
        log("协程执行完成")
    }
    log("主线程执行代码")
}
复制代码

以上代码执行结果如下: E/MainLog: test1: 主线程执行代码 E/MainLog: test1: 进入协程 E/MainLog: test1: 协程执行完成 结论:launch方法不会阻塞主线程


Async

简单使用

private fun test3() {
    GlobalScope.async {
        log("进入协程async")
        delay(1000)
        log("协程执行完成async")
    }
    log("执行主线程")
}
复制代码

上面代码执行结果如下: E/MainLog: test1: 执行主线程 E/MainLog: test1: 进入协程async E/MainLog: test1: 协程执行完成async 结论:async不会阻塞主线程


launch方法中执行多个async

代码如下:

private fun test4() {
   GlobalScope.launch {
       val result1 = GlobalScope.async {
           delay(2000)
           "result1"
       }
       val result2 = GlobalScope.async {
           delay(1000)
           "result2"
       }
       log("两个async执行结果:${result1.await()} :: ${result2.await()}")
   }
}
复制代码

上面代码中,在launch方法里面执行了两个async方法,得到两个结果,最终通过result.await()获取async执行的结果并打印。:await() 方法只能在一个协程内部调用,在主线程调用会报错 代码中必须两个aysnc函数都返回了结果才会调用打印日志方法


suspend兰布达lambda实现

suspend的返回值在resumeWith中可以使用result.getOrNull() 获得


withContext

withContext不会创建新的协程,可以用来切换调度器的时候使用 代码示例:

private fun test6() {
        GlobalScope.launch(Dispatchers.Main) {//协程在主线程开始
            val image = withContext(Dispatchers.IO) {  // 切换到 IO 线程,并在执行完成后切回 UI 线程
                log("withContext")
            }
//            doSomeThing
            log("toMainThread")
        }
    }
复制代码

上面的代码,协程会在Main主线程创建,但是中间有一部分逻辑需要在子线程中执行,使用withContext实现。withContext中的逻辑执行完成后会再次切换回主线程。   withContext会阻塞协程


协程的挂起suspend

使用suspend修饰的函数在协程内部被调用的时候会让协程进入挂起状态,直到函数执行完成才会结束挂起状态。


协程的取消

fun main() = runBlocking {
    val job1 = launch { // ①
        log(1)
        delay(1000) // ②
        log(2)
    }
    delay(100)
    log(3)
    job1.cancel() // ③
    log(4)
}
复制代码

上面代码的输出结果:1、3、4 因为delay是可以响应取消的,而job1被取消了所以不能输出2 如下代码,我们将job1中的delay加一个try/catch:那么输出会变为: 1、3、4、 cancelled. kotlinx.coroutines.JobCancellationException: Job was cancelled; job=StandaloneCoroutine{Cancelling}@e73f9ac、 2 也就是说我们调用job1.cancel的时候delay会抛出异常从而中断协程


fun main() = runBlocking {
    val job1 = launch { // ①
        log(1)
        try {
             delay(1000)
        }catch (e:Exception){
            log("cancelled. $e")// ②
        }
        log(2)
    }
    delay(100)
    log(3)
    job1.cancel() // ③
    log(4)
}
复制代码


kotlin中的Thread使用方法

有两种方式创建线程,方法如下:

val thread = thread {
}
val thread1 = thread(start = false) {
}
thread1.start()
复制代码

默认无参的方式就会自动启动,也可以手动启动(必须设置start=false)


GlobalScope的使用注意点

Global scope 通常用于启动顶级协程,这些协程在整个应用程序生命周期内运行,不会被过早地被取消。程序代码通常应该使用自定义的协程作用域。直接使用 GlobalScope 的 async 或者 launch 方法是强烈不建议的 GlobalScope 创建的协程没有父协程,GlobalScope 通常也不与任何生命周期组件绑定。除非手动管理,否则很难满足我们实际开发中的需求。所以,GlobalScope 能不用就尽量不用。 比如如果在我们的activity中使用协程如果用GlobalScope实现,那么退出activity后是无法停止协程的运行的。这个情景下我们应用用MainScope实现是更好的


协程相关知识

协程的创建、start、join、取消、完成

当一个协程创建后它就处于新建(New)状态,当调用Job的start/join方法后协程就处于活跃(Active)状态,这是运行状态,协程运行出错或者调用Job的cancel方法都会将当前协程置为取消中(Cancelling)状态, 处于取消中状态的协程会等所有子协程都完成后才进入取消 (Cancelled)状态,当协程执行完成后或者调用CompletableJob(CompletableJob是Job的一个子接口)的complete方法都会让当前协程进入完成中(Completing)状态, 处于完成中状态的协程会等所有子协程都完成后才进入完成(Completed)状态。


协程调度器

协程上下文(coroutine context)包含一个协程调度器(参阅 CoroutineDispatcher),协程调度器 用于确定执行协程的目标载体,即运行于哪个线程,包含一个还是多个线程。协程调度器可以将协程的执行操作限制在特定线程上,也可以将其分派到线程池中,或者让它无限制地运行 如下代码中的参数就是一种指定调度器的方式: GlobalScope.async(context = Dispatchers.IO){

} 如果启动协程的方法中没有指定参数,那么协程会从他外部的协程作用域继承上下文和作用域,如下代码: GlobalScope.async{

}


四种调度器介绍Default、IO、Unconfined、Main

参考:blog.csdn.net/c10WTiybQ1Y…


Default、io

Dispatchers.Default和Dispatchers.IO内部都是线程池实现,它们的含义是把协程运行在共享的线程池中


Unconfined

Dispatchers.Unconfined的含义是不给协程指定运行的线程,在第一次被挂起(suspend)之前,由启动协程的线程执行它,但被挂起后, 会由恢复协程的线程继续执行,  如果一个协程会被挂起多次,  那么每次被恢复后,  都有可能被不同线程继续执行 示例:


main

Dispatchers.Main的含义是把协程运行在平台相关的只能操作UI对象的Main线程,所以它根据不同的平台有不同的实现


job的理解

参考:blog.chengyunfeng.com/?p=1087CoroutineScope.launch 函数返回的是一个 Job 对象,代表一个异步的任务。Job 具有生命周期并且可以取消。 Job 还可以有层级关系,一个Job可以包含多个子Job,当父Job被取消后,所有的子Job也会被自动取消;当子Job被取消或者出现异常后父Job也会被取消。 除了通过 CoroutineScope.launch 来创建Job对象之外,还可以通过 Job() 工厂方法来创建该对象。默认情况下,子Job的失败将会导致父Job被取消,这种默认的行为可以通过 SupervisorJob 来修改。 下面的代码演示了子协程中抛出异常父协程会被取消执行的代码

SupervisorJob

暂无


协程启动参数

启动协程需要三样东西,分别是 上下文、启动模式、协程体,协程体 就好比 Thread.run 当中的代码


协程的上下文

子协程的默认上下文: 当一个协程在另外一个协程的协程作用域中启动时,它将通过 CoroutineScope.coroutineContext 继承其上下文,新启动的协程的 Job 将成为父协程的 Job 的子 Job。当父协程被取消时,它的所有子协程也会递归地被取消 但是,当使用 GlobalScope 启动协程时,协程的 Job 没有父级。因此,它不受其启动的作用域和独立运作范围的限制


协程的启动模式

这篇文章把启动模式说的很明白配图说的很好 DEFAULT 立即执行协程体 ATOMIC 立即执行协程体,但在开始运行之前无法取消 UNDISPATCHED 立即在当前线程执行协程体,直到第一个 suspend 调用 LAZY 只有在需要的情况下运行 使用协程模式的代码: GlobalScope.launch(start = CoroutineStart.LAZY){ println("haha") }


协程体

暂无


协程作用域

作用域的一些概念说明

在协程的源代码中有一个接口 CoroutineScope用来指定协程的作用域

CoroutineContext:协程的上下文 MainScope:实现了 CoroutineScope接口 同时是通过调度器调度到了主线程的协程作用域 GlobalScope:实现了CoroutineScope接口 同时执行了一个空的上下文对象的协程作用域 coroutineContext:这通过个方法可以在一个协程中启动协程是承袭他的上下文,同时内部的job将成为外部job 的子job,当一个父协程被取消的时候,所有它的子协程也会被递归的取消。 CoroutineScope(coroutineContext:CoroutineContext):通过传递一个协程上下文实现作用域的创建


假设我们的应用程序有一个具有生命周期的对象,但该对象不是协程。例如,我们正在编写一个Android应用程序,并在Android Activity中启动各种协程,以执行异步操作来获取和更新数据、指定动画等。当 Actovoty 销毁时,必须取消所有协程以避免内存泄漏。当然,我们可以手动操作上下文和 Job 来绑定 Activity 和协程的生命周期。但是,kotlinx.coroutines 提供了一个抽象封装:CoroutineScope。你应该已经对协程作用域很熟悉了,因为所有的协程构造器都被声明为它的扩展函数 我们通过创建与 Activity 生命周期相关联的协程作用域的实例来管理协程的生命周期。CoroutineScope 的实例可以通过 CoroutineScope() 或 MainScope() 的工厂函数来构建。前者创建通用作用域,后者创建 UI 应用程序的作用域并使用 Dispatchers.Main 作为默认的调度器 例,activity作用域的MainScope:

class Activity {
    private val mainScope = MainScope()
    fun destroy() {
        mainScope.cancel()
    }
    // to be continued ...}
复制代码


作用域的代码示例CoroutineScope

1、activity中的协程指定协程作用域只作用于activity生命周期内


协程作用域MainScope

使用MainScope必须引入依赖: implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'


async和launch的异同

launch 与 async 这两个函数大同小异,都是用来在一个 CoroutineScope 内开启新的子协程的。不同点从函数名也能看出来,launch 更多是用来发起一个无需结果的耗时任务(如批量文件删除、创建),这个工作不需要返回结果。async 函数则是更进一步,用于异步执行耗时任务,并且需要返回值(如网络请求、数据库读写、文件读写),在执行完毕通过 await() 函数获取返回值


区别

1、async返回类型为Deferred, launch返回类型为job 2、async可以在协程体中自定义返回值,并且通过Deferred.await堵塞当前线程等待接收async协程返回的类型


其它协程相关的东西

withTimeout

下面这个代码,如果withTimeout内的代码执行时间超过了1300ms,那么会抛出异常

withTimeout(1300L) {
    repeat(1000) { i ->
        println("I'm sleeping $i ...")
        delay(500L)
    }
}
复制代码


withTimeoutOrNull

和withTimeout一样的效果,不过超时的时候不会抛出异常

val result = withTimeoutOrNull(1300L) {
    repeat(1000) { i ->
        println("I'm sleeping $i ...")
        delay(500L)
    }
    "Done" // 在它运行得到结果之前取消它
}
println("Result is $result")
复制代码


suspendCoroutine

join方法妙用


需要调研的知识


suspend关键字(未开始)

Suspend 挂起函数


协程中更新ui方法


使用MainScope更新ui

使用withContext切换到主线程更新ui

协程用Dispatcher.Main创建协程可以更新ui




相关文章
|
5月前
|
Go Python
使用python实现一个用户态协程
【6月更文挑战第28天】本文探讨了如何在Python中实现类似Golang中协程(goroutines)和通道(channels)的概念。文章最后提到了`wait_for`函数在处理超时和取消操作中的作
52 1
使用python实现一个用户态协程
|
6月前
|
安全 调度 Python
探索Python中的并发编程:协程与多线程的比较
本文将深入探讨Python中的并发编程技术,重点比较协程与多线程的特点和应用场景。通过对协程和多线程的原理解析,以及在实际项目中的应用案例分析,读者将能够更好地理解两种并发编程模型的异同,并在实践中选择合适的方案来提升Python程序的性能和效率。
|
2月前
|
调度 Python
python3 协程实战(python3经典编程案例)
该文章通过多个实战案例介绍了如何在Python3中使用协程来提高I/O密集型应用的性能,利用asyncio库以及async/await语法来编写高效的异步代码。
22 0
|
4月前
|
数据库 开发者 Python
实战指南:用Python协程与异步函数优化高性能Web应用
【7月更文挑战第15天】Python的协程与异步函数优化Web性能,通过非阻塞I/O提升并发处理能力。使用aiohttp库构建异步服务器,示例代码展示如何处理GET请求。异步处理减少资源消耗,提高响应速度和吞吐量,适用于高并发场景。掌握这项技术对提升Web应用性能至关重要。
86 10
|
4月前
|
数据处理 Python
深入探索:Python中的并发编程新纪元——协程与异步函数解析
【7月更文挑战第15天】Python 3.5+引入的协程和异步函数革新了并发编程。协程,轻量级线程,由程序控制切换,降低开销。异步函数是协程的高级形式,允许等待异步操作。通过`asyncio`库,如示例所示,能并发执行任务,提高I/O密集型任务效率,实现并发而非并行,优化CPU利用率。理解和掌握这些工具对于构建高效网络应用至关重要。
51 6
|
4月前
|
大数据 数据处理 API
性能飞跃:Python协程与异步函数在数据处理中的高效应用
【7月更文挑战第15天】在大数据时代,Python的协程和异步函数解决了同步编程的性能瓶颈问题。同步编程在处理I/O密集型任务时效率低下,而Python的`asyncio`库支持的异步编程利用协程实现并发,通过`async def`和`await`避免了不必要的等待,提升了CPU利用率。例如,从多个API获取数据,异步方式使用`aiohttp`并发请求,显著提高了效率。掌握异步编程对于高效处理大规模数据至关重要。
54 4
|
4月前
|
设计模式 机器学习/深度学习 测试技术
设计模式转型:从传统同步到Python协程异步编程的实践与思考
【7月更文挑战第15天】探索从同步到Python协程异步编程的转变,异步处理I/O密集型任务提升效率。async/await关键词定义异步函数,asyncio库管理事件循环。面对挑战,如思维转变、错误处理和调试,可通过逐步迁移、学习资源、编写测试和使用辅助库来适应。通过实践和学习,开发者能有效优化性能和响应速度。
54 3
|
4月前
|
调度 Python
揭秘Python并发编程核心:深入理解协程与异步函数的工作原理
【7月更文挑战第15天】Python异步编程借助协程和async/await提升并发性能,减少资源消耗。协程(async def)轻量级、用户态,便于控制。事件循环,如`asyncio.get_event_loop()`,调度任务执行。异步函数内的await关键词用于协程间切换。回调和Future对象简化异步结果处理。理解这些概念能写出高效、易维护的异步代码。
58 2
|
4月前
|
Python
从零到一:构建Python异步编程思维,掌握协程与异步函数
【7月更文挑战第15天】Python异步编程提升效率,通过协程与异步函数实现并发。从async def定义异步函数,如`say_hello()`,使用`await`等待异步操作。`asyncio.run()`驱动事件循环。并发执行任务,如`asyncio.gather()`同时处理`fetch_data()`任务,降低总体耗时。入门异步编程,解锁高效代码。
78 1
|
5月前
|
数据挖掘 程序员 调度
Python并发编程之协程与异步IO
传统的多线程和多进程模型在处理大规模并发时存在一些性能瓶颈和资源消耗问题。本文将重点介绍Python中基于协程和异步IO的并发编程方法,探讨其工作原理和实际应用,帮助开发者更好地理解并利用Python的并发编程能力。