You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 22:07:55