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

x33g5p2x  于2022-03-08 转载在 其他  
字(4.4k)|赞(0)|评价(0)|浏览(646)
  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()的时间。那么怎么实现协程的高效并发呢?

  1. 使用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

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

  • 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。

  • 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
  • 不会阻塞当前线程
  • 会等待,当计算完毕时,恢复执行
  • 会返回结果值或者由于被取消而对应的异常
  • 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()才会触发协程的调度。

相关文章