Kotlin协程启动(2)

5 篇文章 3 订阅
5 篇文章 0 订阅

在说协程启动之前来看看线程启动的代码:

  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)
    }
  1. Thread.interrupt() 设置当前中断标记为true
  2. 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(挂起)函数,这个下章讲...

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值