前言
Kotlin 协程是一种用于编写异步和并发代码的强大工具,它简化了异步任务的处理和管理。在 Kotlin 协程中,launch
和 async
是两个用于创建并发任务的关键构建块。尽管它们都用于启动协程,但它们在用法上有一些区别。
相似点
首先,让我们来看看 launch
和 async
之间的相似之处:
都用于启动协程
launch
和 async
都用于启动一个新的协程,让它在后台执行某些任务。
都可以返回 Job
对象
无论您是使用 launch
还是 async
,都会返回一个 Job
对象,它代表了协程的生命周期和状态。这允许您跟踪和管理协程。 代码示例: launch
suspend fun testLaunch1() {
val job = GlobalScope.launch {
log(delay(200))
log("launch--1")
}
job.join()
}
async
suspend fun testAsync1() {
val job = GlobalScope.async {
delay(200)
log("async--1")
"jjj"
}
job.join()
// log("result-->${job.await()}")
}
都支持协程的取消
无论您是使用 launch
还是 async
,您都可以使用返回的 Job
对象来取消正在运行的协程,以便在需要时停止它们的执行。
launch cancel
suspend fun testLaunchCancel() {
val job = GlobalScope.launch {
log(delay(200))
log("launch--1")
}
job.cancel()
log("cancel")
}
/**
* async cancel
*/
suspend fun testAsyncCancel() {
val job = GlobalScope.async {
delay(200)
log("async--1")
"jjj"
}
job.cancel()
log("cancel")
}
不同点
尽管 launch
和 async
有一些相似之处,但它们在以下方面有着明显的不同:
返回值
-
launch
不返回任何结果,它只是启动一个协程并在后台执行任务。通常情况下,launch
用于执行不需要返回值的任务,例如在后台执行一些操作而不需要等待结果。 -
async
返回一个Deferred
对象,该对象包装了异步计算的结果。您可以使用await
函数来获取这个结果。因此,async
用于执行需要返回值的任务,您可以等待它完成并获取其结果。
async 带返回值:
/**
* async 带返回值
*/
suspend fun testAsync1() {
val deferred = GlobalScope.async {
delay(200)
log("async--1")
"jjj"
}
log("result-->${deferred.await()}")
}
由于async 带返回值,常用于以下并发场景:
/**
* 并发获取user
*/
suspend fun testConcurrentAsync() {
val userList = listOf(1, 2, 3, 4, 5)
runBlocking {
val costTime = measureTimeMillis {
val deferred = userList.map {
async {
ClientManager.getUserAsync(it)
}
}
val result = deferred.awaitAll()
log("result -->${result.toString()}")
}
log("costTime-->${costTime}")
}
}
其中 ClientManager
/**
* 模拟客户端请求
*/
object ClientManager {
var executor: Executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2)
val customDispatchers = executor.asCoroutineDispatcher()
/**
* getUser
*/
fun getUser(userId: Int, callback: (User) -> Unit) {
executor.execute {
val sleepTime = Random().nextInt(500)
Thread.sleep(sleepTime.toLong())
callback(User(userId, sleepTime.toString(), "$userId-->avatar", ""))
}
}
/**
* getAvatar
*/
fun getUserAvatar(user: User, callback: (User) -> Unit) {
executor.execute {
val sleepTime = Random().nextInt(1000)
try {
Thread.sleep(sleepTime.toLong())
} catch (e: InterruptedException) {
e.printStackTrace()
}
user.file = "$sleepTime.png"
callback(user)
}
}
/**
* 异步同步化
*/
suspend fun getUserAsync(userId: Int): User = suspendCoroutine { continuation ->
getUser(userId) {
continuation.resume(it)
}
}
}
相比于java 版本的并发Kotlin async没有加锁的过程,下面是用CountDownLatch并发获取User的场景:
fun main() {
val userList = listOf(1, 2, 3, 4, 5)
val timeCost = measureTimeMillis {
val countDownLatch = CountDownLatch(userList.size)
userList.forEach {
ClientManager.getUser(it) {
countDownLatch.countDown()
}
}
countDownLatch.await()
}
log("timeCost-->$timeCost")
}
异常处理:
-
launch
不会传播异常给调用者。如果在launch
中发生异常,它会被协程框架捕获并记录,但不会抛出到调用方。这意味着您需要明确捕获并处理异常。
suspend fun testError() {
val job = GlobalScope.launch {
try {
getUserError()
} catch (e: Exception) {
log("error->${e.message}")
}
}
job.join()
}
-
async
会传播异常给调用者。如果在async
中发生异常,您需要使用await
函数来获取结果,这可能会抛出已发生的异常。这使得异步任务中的异常更容易被捕获和处理。
总结
- launch 和 async 都是Kotlin 协程中的关键构建块,用于并发任务。它们的主要区别在于返回值和用途。
- launch 用于无返回值的任务,而async 用于需要返回值的任务。
- 异常区别在于异常的处理时机和返回结果的方式。在 launch 中,异常会立即抛出,而在 async 中,异常会被延迟到调用 await 方法时抛出。