Kotlin 协程 基本认识
Posted 匆忙拥挤repeat
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin 协程 基本认识相关的知识,希望对你有一定的参考价值。
文章目录
I. 协程(coroutine) kotlin的线程切换框架
典型的应用:
- 开启一个协程,指定 CoroutineContex 协程上下文(包含运行的线程及线程池等)
- 内部包含其它协程(这样的操作较少)、挂起函数(较多)、多线程操作代码(较多)等;
- 遇到挂起函数,会进入到函数内,切换线程到挂起函数指定的线程,并执行完后,再切回原线程,继续执行。
II. 挂起函数 suspend fun
挂起函数,会挂起协程;挂起,不是阻塞。
最直接的用法就是在主线程中,遇到一个挂起函数,进入到挂起函数内去执行;
如果该挂起函数,又被一个协程指定了线程,那么会切换线程运行;
不管切不切线程,挂起函数执行完后,才继续向下执行。
但是,若不是由一个子协程来指定切线程,那么就不会等到该线程完全执行完毕的。(如使用了Thread
、ThreadPoolExecutor
来启动线程)
decompile 会看到, 挂起函数封装成 Continuation
对象,通过Continuation#resumeWith(result:Result<T>)
获取结果,result.isFailure/isSuccess
挂起函数 失败/成功;外部拿到挂起函数的结果后,再向后执行。
suspend 本身不能让函数被挂起。结合运用在协程内,或在其它挂起函数内来执行挂起操作。
挂起函数,要求被一个协程或另一个挂起函数调用,最终的目的,还是要被协程来运行。
suspend fun m()
//withContext 是一个挂起函数
withContext(Dispatchers.IO)
...
挂起函数一旦挂起协程(不一定会切换线程),就会暂停协程代码不向下运行,直到挂起函数执行完。
withContext(),kotlin提供的一个挂起函数:
public suspend fun <T> withContext(
context: kotlin.coroutines.CoroutineContext,
block: suspend kotlinx.coroutines.CoroutineScope.() -> T
) : T
III. 创建协程
a. 协程作用域 CoroutineScope
创建 CoroutineScope:
runBlocking()
阻塞式。会等到内部所有子协程与挂起函数执行完成。内部会创建BlockingCoroutine
阻塞式协程对象MainScope()
标准库中并没有具体的实现(写个控制台demo会报错)。android中是有的。主线程的协程作用域。GlobalScope
是一个 object class。全局作用域。它不会随父级的非全局作用域的取消而取消。- 自定义实现
class MyCoroutineScope(
override val coroutineContext: CoroutineContext
) : CoroutineScope
CoroutineScope
的扩展函数:
launch()
非阻塞。默认依赖 CoroutineScope 的 CoroutineContext。返回 Job 。内部会创建 StandaloneCoroutine对象(它实现了Job, Continuation, CoroutineScope )。async()
非阻塞。会创建新的 CoroutineContext。内部创建DeferredCoroutine对象(它们Deferred, Job, Continuation, CoroutineScope DefaultDispatcher)
b. launch()
public fun kotlinx.coroutines.CoroutineScope.launch(
context: kotlin.coroutines.CoroutineContext,
start: kotlinx.coroutines.CoroutineStart,
block: suspend kotlinx.coroutines.CoroutineScope.() -> kotlin.Unit
) : kotlinx.coroutines.Job
关于参数:
context
: CoroutineContext 协程上下文
start
: CoroutineStart 协程启动模式
block
: 一个挂起的、CoroutineScope 的、 匿名的扩展函数。内部由函数 coroutineScope(block)
创建 CoroutineScope
协程作用域 对象。
注: CoroutineScope.() 就像扩展函数一样声明的,只是没有函数名。然而通过测试发现,block的调用,实际上需要传入的是一个CoroutineScope对象,所以在调用block函数的时候会创建CoroutineScope对象。
写了个例子,
fun test(v: Int, block: Int.() -> Double)
println(block(v * 2)) //println 2048.0
fun calc(a: Int): Double
return (a shl 10) * 1.0 //2<<10 = 2048
test(1)
calc(this)
//18.5
调用test(),传入一个int参数,和一个函数; 即 calc(this) 这个整体就是 test中的 block参数;
test() 实现中,打印 block()的返回值; block 需要一个Int形参数,这里对 v 乘以 2;
block <==> calc(this) ,这时this = 2,calc函数运行后得到 2048.0;
最后,block 的返回值 2048.0 被 println() 出来
如果把注释中的 “18.5” 语句打开,那它就是 block() 的返回值。 kotlin 语法,在这里会把最后一行代码的结果当成返回值。
c. async()
public fun <T> kotlinx.coroutines.CoroutineScope.async(
context: kotlin.coroutines.CoroutineContext,
start: kotlinx.coroutines.CoroutineStart,
block: suspend kotlinx.coroutines.CoroutineScope.() -> T
): kotlinx.coroutines.Deferred<T>
d. runBlocking()
public fun <T> runBlocking(
context: kotlin.coroutines.CoroutineContext,
block: suspend kotlinx.coroutines.CoroutineScope.() -> T
): T
这几个函数在声明时,context 、start 都赋予了默认值。
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
e. 协程启动模式 CoroutineStart
public enum class CoroutineStart
DEFAULT, //默认的模式,立即执行协程体
LAZY, //只有在需要的情况下运行
@ExperimentalCoroutinesApi //实验性 协程 api
ATOMIC, //
@ExperimentalCoroutinesApi
UNDISPATCHED; //
f. Job
主要成员函数:
jon()
等待协程执行完。( 类似 Thread#join() )cancel()
取消协程。协程中的所有挂起函数都是可取消的。它们检查协程的取消,并在取消时抛出CancellationException。cancelAndJoin()
内部就是先cancel()再join()。等待取消操作完成
g. Deferred
它是Job的子接口。 主要有一个 await()
。
await()
是一个 suspend 函数,它有返回类型<T>。
调用了它,就会挂起协程,执行完后获得一个结果 T 。
h. Dispatchers 调度器
实现自 CoroutineContext 。
public actual object Dispatchers
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
public actual val Main: MainCoroutineDispatcher get() =
MainDispatcherLoader.dispatcher
public actual val Unconfined: CoroutineDispatcher =
kotlinx.coroutines.Unconfined
public val IO: CoroutineDispatcher = DefaultScheduler.IO
Main 安卓中能使用,即 UI 线程;控制台中不能使用,未实现。
IO 内部线程池中的一个dispatcher线程;执行磁盘或网络 I/O 密集型;
Default 内部线程池中的一个dispatcher线程,执行cpu 密集型;
Unconfined 非限制
i. 其它挂起函数
delay()
协程挂起多少毫秒。( 类似 Thread.sleep() )yield()
如果可能,将当前协程Dispatchers的线程(或线程池)提供给其他协程序运行。 ( 类似 Thread.yield() ) 。 如下例,会交错输出:
// 同是 Main、Default、Unconfined,才会在 yield 切换运行的协程时,在同一线程中。
// IO 会创建新线程。 Default 内部是一个线程池。
runBlocking
launch(Dispatchers.Unconfined, CoroutineStart.LAZY)
for (i in 0..3)
println("aaaa $Thread.currentThread().name")
yield()
launch(Dispatchers.Unconfined, CoroutineStart.LAZY)
for (i in 0..3)
println("bbbb $Thread.currentThread().name")
yield()
IV. 使用流程
- 先要创建协程作用域(同时,内部就会创建一个协程)
- 协程作用域内,除普通代码外,可以含有其它协程与挂起函数(当然挂起函数内也可以再有子协程)
- 可以指定 CoroutineContext、Dispatchers、CoroutineStart
- 是否需要对协程进行 join、cancel 操作,还是要 await 结果;以此来选择使用 lanuch()还是async();
Deferred 是 Job的子类,也有 join、cancel 操作,其 - withContext 是一个挂起函数。 使用它,即不关心 协程的 join、cancel、await 操作
开发者涨薪指南 48位大咖的思考法则、工作方式、逻辑体系
以上是关于Kotlin 协程 基本认识的主要内容,如果未能解决你的问题,请参考以下文章