Kotlin学习系列之:使用async和await实现协程高效并发

  1. 引例:

    private suspend fun intValue1(): Int {
        delay(1000)
        return 1
    }
    
    private suspend fun intValue2(): Int {
        delay(2000)
        return 2
    }
    
    fun main() = runBlocking {
    
        val elapsedTime = measureTimeMillis {
            val value1 = intValue1()
            val value2 = intValue2()
    
            println("the result is ${value1 + value2}")
        }
    
        println("the elapsedTime is $elapsedTime")
    }
    

    先介绍一下measureTimeMillis{}:

    /**
     * Executes the given [block] and returns elapsed time in milliseconds.
     */
    public inline fun measureTimeMillis(block: () -> Unit): Long {
        val start = System.currentTimeMillis()
        block()
        return System.currentTimeMillis() - start
    }
    

    用来计算代码的执行时间的简便方法。然后来看输出结果:

    the result is 3
    the elapsedTime is 3018

    这个例子想要说明什么问题呢?要想实现功能A,需要依赖于功能B和功能C的结果,如果功能B和功能C之间又有依赖关系,那么只能B和C顺序执行;但是如果B和C是完全独立的,那么B和C就可以同时进行,从而缩短实现功能A所需时间。就好像本例中的intValue1()和intValue2(),它们是完全独立的,但是从运行结果来看,耗费的时间是intValue1()的时间 + intValue2()的时间。那么怎么实现协程的高效并发呢?

  2. 使用async和await改善代码:

    fun main() = runBlocking {
    
        val elapsedTime = measureTimeMillis {
            val value1 = async { intValue1() }
            val value2 = async { intValue2() }
    
            println("the result is ${value1.await() + value2.await()}")
        }
    
        println("the elapsedTime is $elapsedTime")
    }
    
    private suspend fun intValue1(): Int {
        delay(1000)
        return 1
    }
    
    private suspend fun intValue2(): Int {
        delay(2000)
        return 2
    }
    

    输出结果为:

    the result is 3
    the elapsedTime is 2016

    从结果上看,所耗时间变短了,执行效率也就提高了。

  3. async:

    public fun <T> CoroutineScope.async(
        context: CoroutineContext = EmptyCoroutineContext,
        start: CoroutineStart = CoroutineStart.DEFAULT,
        block: suspend CoroutineScope.() -> T
    ): Deferred<T>
    
    Creates a coroutine and returns its future result as an implementation of [Deferred].
    The running coroutine is cancelled when the resulting deferred is [cancelled][Job.cancel].
    

    通过aysnc的方法签名以及它的文档注释,我们可以得知:

    • 它和CoroutineScope.launch{}类似,可以用来创建一个协程,不同的是launch的返回结果是Job类型,而aysnc的返回结果是Deferred类型
    • 结果Deferred和Job一样,也能够被取消。

    至此,我们创建协程的方式就有了五种:

    • GlobalScope.launch{}
    • launch{}
    • runBlocking{}
    • coroutineScope{}
    • async{}

    那么Deferred到底是个什么东东?它和Job之间又有怎样的联系和区别?

    public interface Deferred<out T> : Job 
    
    Deferred value is a non-blocking cancellable future &mdash; it is a [Job] with a result
    

    从类的层次结构上看,Deferred是Job的子接口;从功能上来看,Deferred就是带返回结果的Job。

  4. await: Deferred接口定义的方法

    Awaits for completion of this value without blocking a thread and resumes when deferred computation is complete,returning the resulting value or throwing the corresponding exception if the deferred was cancelled
    
    • 不会阻塞当前线程
    • 会等待,当计算完毕时,恢复执行
    • 会返回结果值或者由于被取消而对应的异常
  5. CoroutineScope.async()中的start参数:默认值为CoroutineStart.DEFAULT

    /**
     * Defines start options for coroutines builders.
     * It is used in `start` parameter of [launch][CoroutineScope.launch], [async][CoroutineScope.async], and other coroutine builder functions.
     *
     * The summary of coroutine start options is:
     * * [DEFAULT] -- immediately schedules coroutine for execution according to its context;
     * * [LAZY] -- starts coroutine lazily, only when it is needed;
     * * [ATOMIC] -- atomically (in a non-cancellable way) schedules coroutine for execution according to its context;
     * * [UNDISPATCHED] -- immediately executes coroutine until its first suspension point _in the current thread_.
     */
    public enum class CoroutineStart {
    		DEFAULT,
    		LAZY,
    		ATOMIC,
    		UNDISPATCHED
    }
    
    • DEFAULT: 默认值,它会上下文立即调度线程的执行
    • LAZY:它不会立即调度协程的执行,而是在需要的时候才会触发执行
    • ATOMIC:原子性调度,即不会被取消
    • UNDISPATCHED:也会立即调度,直到当前的第一个挂起点,这个后面讨论分发器的时候还会说

    这个参数在launch里面也有,表达的含义是一致的。

    我们现在试一下LAZY的使用:

    fun main() = runBlocking {
    
        val elapsedTime = measureTimeMillis {
          
            val intValue1 = async(start = CoroutineStart.LAZY) { intValue1() }
            val intValue2 = async(start = CoroutineStart.LAZY) { intValue2() }
    
            println("hello world")
    
            val result1 = intValue1.await()
            val result2 = intValue2.await()
    
            println("the result is : ${result1 + result2}")
        }
    
        println("elapsedTime = $elapsedTime")
    }
    
    private suspend fun intValue1(): Int {
        delay(1000)
        return 15
    }
    
    private suspend fun intValue2(): Int {
        delay(2000)
        return 20
    }
    

    hello world
    the result is : 35
    elapsedTime = 3018

    一旦添加了这个参数之后,我们会发现,async的并发失效了。结合前面的阐述,我们不难得出结论:由于CoroutineStart.LAZY的作用,我们async启动的两个协程并没有立即执行。而是直到调用await方法之后,才开始执行,而await又是会去等待结果,自然需要等待intValue1协程执行完毕后,遇到intValue2.await(),才会触发intValue2协程的执行,又要去等待。那么,async的并发也就失效了。

    fun main() = runBlocking {
      val elapsedTime = measureTimeMillis {
    
          val intValue1 = async(start = CoroutineStart.LAZY) { intValue1() }
          val intValue2 = async(start = CoroutineStart.LAZY) { intValue2() }
    
          println("hello world")
    
          intValue1.start()
          intValue2.start()
    
          val result1 = intValue1.await()
          val result2 = intValue2.await()
    
          println("the result is : ${result1 + result2}")
    }
    
    println("elapsedTime = $elapsedTime")
    }
    
    private suspend fun intValue1(): Int {
        delay(1000)
        return 15
    }
    
    private suspend fun intValue2(): Int {
        delay(2000)
        return 20
    }
    

    我们可以通过Deferred的start方法,手动触发协程的执行,输出结果如下:

    hello world
    the result is : 35
    elapsedTime = 2013

    换句话说,使用了CoroutineStart.LAZY参数之后,协程不会立马执行,直到调用了start()或await()才会触发协程的调度。

你可能感兴趣的:(Kotlin系列教程)