非runBlocking下从常规函数调用Kotlin挂起函数并获结果可行吗?
生产环境下替代runBlocking从同步函数调用挂起函数的方案
问题背景
YouTube视频及Kotlin官方文档均建议runBlocking仅用于测试场景,而非生产代码。但目前我找不到无需runBlocking,从常规同步函数调用挂起函数并获取结果的方法。
应用场景
Camunda BPMN触发Kotlin实现的Camunda委托类,该类需调用挂起函数并发调用两个API并获取结果,随后将结果存入Camunda执行变量。当前用runBlocking可正常运行,但不符合生产代码最佳实践。
当前实现代码
CamundaInitialDelegate
import org.camunda.bpm.engine.delegate.DelegateExecution import org.camunda.bpm.engine.delegate.JavaDelegate import org.springframework.stereotype.Component @Component class CamundaInitialDelegate( private val businessService: BusinessService ) : JavaDelegate { override fun execute(execution: DelegateExecution) { val result = businessService.businessFunction() execution.setVariable("result-of-initial-delegate", result.joinToString()) } }
BusinessService
import kotlinx.coroutines.* import org.springframework.stereotype.Service @Service class BusinessService { fun businessFunction(): List<Int> { return runBlocking { // 此处需替代runBlocking的方案 val result = suspendApiCall() // 调用挂起函数 onSuspendingFunctionCompleted(result) // 用结果调用非挂起函数 } } private suspend fun suspendApiCall(): List<Int> { val scope = CoroutineScope(Dispatchers.IO) val async1 = scope.async { delay(2000) // 模拟API调用 42 } val async2 = scope.async { delay(3000) // 模拟另一个API调用 43 } val listOfDeferred = listOf(async1, async2) return listOfDeferred.awaitAll() } private fun onSuspendingFunctionCompleted(result: List<Int>): List<Int> { println("The result is $result") return result } }
现有逻辑说明
将suspendApiCall设为挂起函数是为实现两个API的并发调用,总耗时从约5秒缩短至3秒。此为常规Web应用,非Android应用。
替代方案
方案1:Spring @Async + CompletableFuture
利用Spring原生异步支持实现并发,无需依赖协程:
修改BusinessService:
import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Service import java.util.concurrent.CompletableFuture @Service class BusinessService { fun businessFunction(): List<Int> { val future1 = apiCall1() val future2 = apiCall2() val result1 = future1.get() val result2 = future2.get() val result = listOf(result1, result2) return onSuspendingFunctionCompleted(result) } @Async fun apiCall1(): CompletableFuture<Int> { Thread.sleep(2000) // 模拟API调用 return CompletableFuture.completedFuture(42) } @Async fun apiCall2(): CompletableFuture<Int> { Thread.sleep(3000) // 模拟API调用 return CompletableFuture.completedFuture(43) } private fun onSuspendingFunctionCompleted(result: List<Int>): List<Int> { println("The result is $result") return result } }
注意:需在Spring Boot主类添加@EnableAsync注解开启异步支持。
方案2:CoroutineScope + Future阻塞等待
保留协程并发逻辑,通过CompletableFuture包装协程结果,避免runBlocking:
修改BusinessService:
import kotlinx.coroutines.* import org.springframework.stereotype.Service import java.util.concurrent.CompletableFuture @Service class BusinessService { // 自定义协程作用域,可替换为Spring TaskExecutor对应的Dispatcher private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) fun businessFunction(): List<Int> { val future = coroutineScope.future { suspendApiCall() } val result = future.get() // 阻塞等待协程完成 return onSuspendingFunctionCompleted(result) } private suspend fun suspendApiCall(): List<Int> { val async1 = coroutineScope.async { delay(2000) // 模拟API调用 42 } val async2 = coroutineScope.async { delay(3000) // 模拟另一个API调用 43 } return listOf(async1, async2).awaitAll() } private fun onSuspendingFunctionCompleted(result: List<Int>): List<Int> { println("The result is $result") return result } }
此方式相比runBlocking更灵活,可自定义协程调度器与作用域,减少线程阻塞风险。
方案3:Camunda异步委托
若业务允许任务异步执行,可将Camunda任务设为异步,直接使用挂起函数:
修改CamundaInitialDelegate:
import org.camunda.bpm.engine.delegate.DelegateExecution import org.camunda.bpm.engine.delegate.JavaDelegate import org.springframework.stereotype.Component import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @Component class CamundaInitialDelegate( private val businessService: BusinessService ) : JavaDelegate { // 需在Camunda模型中将该任务设置为异步执行 override fun execute(execution: DelegateExecution) { val coroutineScope = CoroutineScope(Dispatchers.IO) coroutineScope.launch { val result = businessService.suspendBusinessFunction() execution.setVariable("result-of-initial-delegate", result.joinToString()) // 异步任务需注意Camunda执行上下文与事务管理 } } }
修改BusinessService,将业务方法改为挂起函数:
import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.awaitAll import org.springframework.stereotype.Service @Service class BusinessService { suspend fun suspendBusinessFunction(): List<Int> { val result = suspendApiCall() return onSuspendingFunctionCompleted(result) } private suspend fun suspendApiCall(): List<Int> { val async1 = async { delay(2000) // 模拟API调用 42 } val async2 = async { delay(3000) // 模拟另一个API调用 43 } return listOf(async1, async2).awaitAll() } private fun onSuspendingFunctionCompleted(result: List<Int>): List<Int> { println("The result is $result") return result } }
此方案完全避免线程阻塞,适合允许任务异步执行的场景。
方案对比
- 方案1:无协程依赖,Spring原生支持,学习成本低,适合不熟悉协程的场景。
- 方案2:保留协程优势,更灵活的作用域控制,适合希望继续使用协程的场景。
- 方案3:利用Camunda异步特性,彻底消除阻塞,适合允许任务异步执行的业务场景。
内容的提问来源于stack exchange,提问作者firstpostcommenter
相关产品推荐
相关产品推荐

