典型的应用:
挂起函数,会挂起协程;挂起,不是阻塞。
最直接的用法就是在主线程中,遇到一个挂起函数,进入到挂起函数内去执行;
如果该挂起函数,又被一个协程指定了线程,那么会切换线程运行;
不管切不切线程,挂起函数执行完后,才继续向下执行。
但是,若不是由一个子协程来指定切线程,那么就不会等到该线程完全执行完毕的。(如使用了Thread
、ThreadPoolExecutor
来启动线程)
decompile 会看到, 挂起函数封装成 Continuation
对象,通过Continuation#resumeWith(result:Result
获取结果,result.isFailure/isSuccess
挂起函数 失败/成功;外部拿到挂起函数的结果后,再向后执行。
suspend 本身不能让函数被挂起。结合运用在协程内,或在其它挂起函数内来执行挂起操作。
挂起函数,要求被一个协程或另一个挂起函数调用,最终的目的,还是要被协程来运行。
suspend fun m() {
//withContext 是一个挂起函数
withContext(Dispatchers.IO) {
...
}
}
挂起函数一旦挂起协程(不一定会切换线程),就会暂停协程代码不向下运行,直到挂起函数执行完。
public suspend fun <T> withContext(
context: kotlin.coroutines.CoroutineContext,
block: suspend kotlinx.coroutines.CoroutineScope.() -> T
) : T { }
创建 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)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 语法,在这里会把最后一行代码的结果当成返回值。
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> { }
public fun <T> runBlocking(
context: kotlin.coroutines.CoroutineContext,
block: suspend kotlinx.coroutines.CoroutineScope.() -> T
): T { }
这几个函数在声明时,context 、start 都赋予了默认值。
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
public enum class CoroutineStart {
DEFAULT, //默认的模式,立即执行协程体
LAZY, //只有在需要的情况下运行
@ExperimentalCoroutinesApi //实验性 协程 api
ATOMIC, //
@ExperimentalCoroutinesApi
UNDISPATCHED; //
}
主要成员函数:
jon()
等待协程执行完。( 类似 Thread#join() )cancel()
取消协程。协程中的所有挂起函数都是可取消的。它们检查协程的取消,并在取消时抛出CancellationException。cancelAndJoin()
内部就是先cancel()再join()。等待取消操作完成它是Job的子接口。 主要有一个 await()
。
await()
是一个 suspend 函数,它有返回类型
调用了它,就会挂起协程,执行完后获得一个结果 T 。
实现自 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 非限制
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()
}
}
}