Kotlin | 协程async、await机制实现串行&并行请求


在 Kotlin 中,协程(Coroutines)提供了一种轻量级的并发编程方式。如果还不了解协程,可以参见之前的文章: Kotlin | 深入理解协程。本文来聊一聊协程中 asyncawait 机制来高效处理并发异步任务。

async 和 await 机制

  • asyncasync 函数用于启动一个协程,这一点与launch的作用是一样的,不同的是,async 还会返回一个 Deferred 对象,它代表了一个未来会产生结果的值。异步任务可以在 async 函数中通过 suspend 修饰的挂起函数来执行。
//async源码
public fun <T> CoroutineScope.async(
   context: CoroutineContext = EmptyCoroutineContext,
   start: CoroutineStart = CoroutineStart.DEFAULT,
   block: suspend CoroutineScope.() -> T
): Deferred<T> {
   val newContext = newCoroutineContext(context)
   val coroutine = if (start.isLazy)
       LazyDeferredCoroutine(newContext, block) else
       DeferredCoroutine<T>(newContext, active = true)
   coroutine.start(start, coroutine, block)
   return coroutine
}
  • awaitawait 是用于等待异步任务完成并获取结果的函数。只能用于等待异步任务返回数据。await 函数会挂起当前协程,直到对应的异步任务完成,并返回任务的结果。

使用示例

示例1:使用 async 和 await 实现并发任务
import kotlinx.coroutines.*

private suspend fun fetchData1(): String {
    delay(1000) //模拟网络请求耗时
    return "data1"
}

private suspend fun fetchData2(): String {
    delay(1500) //模拟网络请求耗时
    return "data2"
}

fun main() = runBlocking {
    val totalTime = measureTimeMillis {
        val deferred1: Deferred<String> = async { fetchData1() }
        val deferred2: Deferred<String> = async (start = CoroutineStart.DEFAULT) { fetchData2() }

        //并发等待两个任务完成,并获取结果
        val result1 = deferred1.await()
        val result2 = deferred2.await()
        println("result1:$result1,result2:$result2")
    }
    println("totalTime:$totalTime")
}

执行结果:
result1:data1,result2:data2
totalTime:1539

在这个示例中,我们定义了两个挂起函数 fetchData1fetchData2,分别模拟了两个网络请求。然后,我们使用 async 启动了两个异步任务,并通过 await 函数等待任务完成并获取结果。最后,打印出两个任务的结果。通过日志可以看到两个任务是并发执行的。

如果想实现两个任务串行执行呢?比如第二个任务需要等第一个任务执行完才能启动,只需要修改async函数的start = CoroutineStart.LAZY即可,如下:

fun main() = runBlocking {
    val totalTime = measureTimeMillis {
        val deferred1: Deferred<String> = async (start = CoroutineStart.LAZY){ fetchData1() }
        val deferred2: Deferred<String> = async (start = CoroutineStart.LAZY){ fetchData2() }
        //...同上...
    }
    println("totalTime:$totalTime")
}

执行结果:
result1:data1,result2:data2
totalTime:2534

可以看到总时间变成了两个任务执行时间的总和,即串行执行。

注: 使用Flow实现同样的串行/并行效果

上面示例中的串行/并行效果,也可以通过flow中的操作符来实现:

  • 对于接口之间的串行关系,可以使用操作符 flatMapConcat实现;
  • 对于接口之间的并行关系,可以使用操作符zip 实现。具体示例参见 Kotlin | Flow数据流的几种使用场景
示例2:使用 async 和 await 实现超时任务
import kotlinx.coroutines.*

suspend fun fetchData(): String {
    delay(2000) // 模拟网络请求耗时
    return "Data"
}

fun main() = runBlocking {
    val deferredResult = withTimeoutOrNull(1500) {
        async { fetchData() }.await()
    }

    if (deferredResult != null) {
        println("Result: $deferredResult")
    } else {
        println("Timeout occurred")
    }
}

在这个示例中,我们使用 withTimeoutOrNull 函数设置了一个超时时间,如果任务在指定时间内没有完成,则返回 null。然后,我们使用 async 启动了一个异步任务,并通过 await 函数等待任务完成。最后,根据任务是否在超时时间内完成来打印结果或者超时信息。

CoroutineStart延迟启动协程

通常,Deferred是在active状态下创建的,即当调用async后就会立即启动协程。然而,async方法中有一个可选的CoroutineStart类型参数,用于表示协程的启动模式,当被设置为CoroutineStart.LAZY时,此时创建的协程并不会立即执行,而是需要通过调用start、join或await来激活(上面示例中的两个接口的串行效果就是通过LAZY实现的)。

public enum class CoroutineStart {
   DEFAULT, LAZY,  ATOMIC, UNDISPATCHED;
}

CoroutineStart 是一个枚举类,下面是对每个枚举值的含义解释,并给出相应的使用示例:

  • DEFAULT:使用默认的协程启动模式,即协程会立即启动,并在当前调度器上执行。
     fun main() = runBlocking {
         val job = GlobalScope.launch(start = CoroutineStart.DEFAULT) {
             println("Coroutine is running")
         }
         job.join() // 等待协程执行完成
     }
  • LAZY:延迟启动协程,直到调用了协程的 start() 方法时才会启动协程。
     fun main() = runBlocking {
         val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
             println("Coroutine is running")
         }
         // 手动启动协程
         job.start()
         job.join() // 等待协程执行完成
     }
  • ATOMIC:以原子方式启动协程,即确保协程立即启动,并且不会被中断。
     fun main() = runBlocking {
         val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
             println("Coroutine is running")
         }
         job.join() // 等待协程执行完成
     }
  • UNDISPATCHED:在当前调度器上立即启动协程,但不会挂起当前协程,而是直接在当前调度器上执行协程的代码。
     fun main() = runBlocking {
         launch(start = CoroutineStart.UNDISPATCHED) {
             println("Coroutine is running")
         }
         println("Coroutine is finished")
     }

这些枚举值提供了不同的协程启动模式,可以根据具体的需求选择合适的启动模式来创建协程。针对串行、并行,可以总结一下:

并行请求: 多个任务都使用async{}或者async(start=CoroutineStart.DEFAULT)来创建协程;
串行请求: 多个任务都使用async(start=CoroutineStart.LAZY)来创建协程并通过deferred.await()等待协程执行结果。

最后再贴一下Coroutine继承关系图

coroutine继承关系图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_小马快跑_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值