在说协程启动之前来看看线程启动的代码:
Thread {
println("线程启动")
}.start()
是不是和协程的启动很像:
GlobalScope.launch {
println("test continuation start")
}
其实很多时候都可以用线程的特性来理解协程。
再看看该方法的完整的签名:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
一,CoroutineContext 协程上下文
@SinceKotlin("1.3")
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
public operator fun plus(context: CoroutineContext): CoroutineContext
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key<E : Element>
public interface Element : CoroutineContext{
public val key: Key<*>
}
}
从方法上面来看,它很像一个集合类的数据集合。
get(key:Key<E>):获取当前key所对应的元素
plus(context:CoroutineContext):给当前集合添加要给元素
minusKey(key:Key<*>):移除当前key所对应的元素
如果你搜索一下就会发现源码中有一个CombinedContext,它的签名internal class CombinedContext(private val left:CoroutineContext, private val element: Element),它是一个左偏序列表,每一个元素对应的类型就是Element。
1、协程的拦截器
@SinceKotlin("1.3")
public interface ContinuationInterceptor : CoroutineContext.Element {
companion object Key : CoroutineContext.Key<ContinuationInterceptor>
public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
public fun releaseInterceptedContinuation(continuation: Continuation<*>) {
/* do nothing by default */
}
}
拦截器是上下文的一个实现方向,拦截器可以左右协程的执行,为了保证它功能的正确性,协程上下文集合永远将它放在最后面。
@Test
fun testInterceptor() = runBlocking {
class ContinuationInterceptorTest(override val key: CoroutineContext.Key<*> = ContinuationInterceptor) :
ContinuationInterceptor {
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
class ContinuationTest(continuation: Continuation<T>) : Continuation<T> {
override val context: CoroutineContext = continuation.context
override fun resumeWith(result: Result<T>) {
println("ContinuationInterceptorTest:${result.getOrNull()}")
val resultValue = result.getOrNull()
if (resultValue == 666) {
continuation.resumeWith(Result.success(90 as T))
} else {
continuation.resumeWith(result)
}
}
}
return ContinuationTest(continuation)
}
}
val job = GlobalScope.launch(ContinuationInterceptorTest()) {
val deferred = async {
delay(1000)
666
}
val result = deferred.await()
println(result)
}
job.join()
println("end")
}
输出:
ContinuationInterceptorTest:kotlin.Unit
ContinuationInterceptorTest:kotlin.Unit
ContinuationInterceptorTest:kotlin.Unit
ContinuationInterceptorTest:666
90
end
这个拦截器将666修改成了90。
2、调度器
public abstract class CoroutineDispatcher :
AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
public abstract fun dispatch(context: CoroutineContext, block: Runnable)
}
调度器也是上下文的一种实现方向,实现了拦截器接口,dispatch方法会在拦截器的方法的interceptContinuation调用,进而实现协程的调度。
@Test
fun testDispatchers() = runBlocking {
class CoroutineDispatcherTest : CoroutineDispatcher(){
override fun dispatch(context: CoroutineContext, block: Runnable) {
Executors.newSingleThreadExecutor { p0 ->
Thread(p0,"我是dispathcer thread")
}.submit(block)
}
}
println("我在哪儿:${Thread.currentThread().name}")
val job = GlobalScope.launch(CoroutineDispatcherTest()) {
println("我在哪儿:${Thread.currentThread().name}")
}
job.join()
println("end")
}
输出:
我在哪儿:main @coroutine#1
我在哪儿:我是dispathcer thread @coroutine#2
end
可以看到已经将协程执行体的线程切换到了自己建立的线程种。
上面是一个线程调度器的简单实现,其实用一个线程池实现调度没必要这么复杂,有更加简单的方法:
Executors.newSingleThreadExecutor().asCoroutineDispatcher().use {
println("我在哪儿:${Thread.currentThread().name}")
val job = GlobalScope.launch(it) {
println("我在哪儿:${Thread.currentThread().name}")
}
job.join()
}
注意自己创建的线程池我们需要在合适的时候关闭它,所以这里用了use函数。
这里我们再使用调度器的时候需要知道线程安全问题在调度器不同的协程之间仍然是存在的,所以要把逻辑尽量控制在一个线程中(要知道协程也是支持并发的),尽量减少对外部作用域变量的引用(可以用参数传递代替),要知道对协程代码加锁并不是一个明智的选择。
一,CoroutineContext 协程启动模式
1、DEFAULT
立即执行协程体,默认就是这种模式
@Test
fun testContinuationStart() = runBlocking {
println("${Thread.currentThread().name}1")
val job = GlobalScope.launch(start = CoroutineStart.DEFAULT) {
println("${Thread.currentThread().name}2")
}
println("${Thread.currentThread().name}3")
job.join()
println("${Thread.currentThread().name}4")
println("end")
}
输出:
main @coroutine#11
main @coroutine#13
DefaultDispatcher-worker-2 @coroutine#22
main @coroutine#14
end
可以注意到CoroutineStart.DEFAULT开了一个线程池去调度协程,调度器准备好了之后就自动执行协程。
2、ATOMIC
立即执行协程体,但是在开始执行之前无法取消
ATOMIC 只有涉及 cancel 的时候才有意义,在解释cancle之前我们先看看线程的interrupt()方法
@Test
fun testThreadInterrupt() {
val thread = object : Thread() {
override fun run() {
for (i in 0..Int.MAX_VALUE) {
println(i)
if (isInterrupted) break
}
}
}
thread.start()
Thread.sleep(100)
thread.interrupt()
Thread.sleep(10*1000)
}
@Test
fun testThreadInterrupt1() {
val thread = object : Thread() {
override fun run() {
for (i in 0..Int.MAX_VALUE) {
println(i)
try {
sleep(1000)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
thread.start()
Thread.sleep(5000)
thread.interrupt()
Thread.sleep(10*1000)
}
- Thread.interrupt() 设置当前中断标记为true
- Thread.isInterrupted() 检测当前的中断标记
interrupt()是将线程设置中断标记为true,可以用isInterrupt()来检查这个标志然后执行相应的逻辑。
然后再看看:
public static void sleep(long millis, int nanos) throws InterruptedException {
...
if (millis == 0 && nanos == 0) {
// ...but we still have to handle being interrupted.
if (Thread.interrupted()) {
throw new InterruptedException();
}
return;
}
...
}
sleep()方法中当Thread.isInterrupted()为true是会抛出InterruptedException。
@Test
fun testContinuationStartAtomic() = runBlocking {
val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
val job = coroutineContext[Job]
for (i in 0..Int.MAX_VALUE) {
println("$i-${job?.isCancelled}")
if (job?.isCancelled == true) {
break
}
}
}
job.cancel()
job.join()
println("end")
}
@Test
fun testContinuationStartAtomic1() = runBlocking {
val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
val job = coroutineContext[Job]
for (i in 0..Int.MAX_VALUE) {
println("$i-${job?.isCancelled}")
try {
delay(100)
} catch (e: Exception) {
e.printStackTrace()
break
}
}
}
job.cancel()
job.join()
println("end")
}
上面的写法是不是和线程的interrupte很像,我想用线程的interrupt类比更加容易理解协程的cancle。
cancel调用一定会将job的状态置为cancelling,ATOMIC模式启动就会无视这个cancel(),直到第一个挂起点然后判断挂起点是否支持cancel()。如果是DEFAULT模式启动之前就cancel那么协程就会被cancel,如果启动之后cancel那么协程的启动不会受到影响。
3、UNDISPATCHED
立即在当前线程执行协程体,直到第一个 suspend 调用
@Test
fun testContinuationStartUndispatched() = runBlocking {
println("${Thread.currentThread().name}-1")
val job = GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) {
println("${Thread.currentThread().name}-2")
delay(100)
println("${Thread.currentThread().name}-x")
}
println("${Thread.currentThread().name}-3")
job.join()
println("${Thread.currentThread().name}-4")
println("end")
}
输出:
main @coroutine#1-1
main @coroutine#2-2
main @coroutine#1-3
DefaultDispatcher-worker-1 @coroutine#2-x
main @coroutine#1-4
end
看输出也就明白了,UNDISPATCHED模式就是直接在当前线程 直接执行直到第一个挂起点,当遇到挂起点之后的执行就取决于挂起点本身的逻辑以及上下文当中的调度器了。
4、LAZY
只有在需要的情况下运行
@Test
fun testContinuationStartLazy() = runBlocking {
println("${Thread.currentThread().name}-1")
val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
println("${Thread.currentThread().name}-2")
delay(100)
println("${Thread.currentThread().name}-x")
}
println("${Thread.currentThread().name}-3")
println("${Thread.currentThread().name}-4")
println("end")
}
输出:
main @coroutine#1-1
main @coroutine#1-3
main @coroutine#1-4
end
可以看到协程体并有没有执行,LAZY就是懒汉启动模式,launch后不会有任何调度行为,协程体自然不会进入执行状态,直到我们需要它执行的时候。那我们需要它执行的时候怎样让它执行呢?答案是:Job.start() 和Job.join()
区别就是start()就是单纯的运行协程,join()不仅运行协程还会等待协程执行完毕。
启动的第三个参数是协程体,协程体是一个suspend(挂起)函数,这个下章讲...