【问题标题】:Kotlin Vertx with Coroutines blocks when trying to call run blocking尝试调用运行阻塞时,带有 Coroutines 的 Kotlin Vertx 阻塞
【发布时间】:2019-04-10 23:59:07
【问题描述】:

我正在使用公开了回调函数的第三方库。回调函数将在成功时调用。回调函数不是挂起函数,但是当我尝试在非挂起函数内部进行调用以返回挂起函数的结果时,该函数使用 aysnc 和 await 进行 IO 调用,调用永远不会被强制执行。下面我想出了一个简单的代码 sn-p 来演示这个问题。

open class TestVerticle: CoroutineVerticle() {

  override suspend fun start() {

    awaitBlockingExample()

  }

 fun awaitBlockingExample():String {

    val future= async(vertx.dispatcher()) {

        makeSuspendFunCall()
     }
     val result:String= runBlocking(vertx.dispatcher()){future.await()}
     println(" The final Result is $result")
     return result
   }

  suspend fun makeSuspendFunCall():String{
    println("Comming here 3")
    delay(500)
    val resp="Test"
    return resp
  }

}
fun main(args: Array<String>) = runBlocking {
    Vertx.vertx().deployVerticle("TestVerticle")
}

如果我删除 makeSuspendFunCall 中的延迟函数,程序运行良好,但如果我添加延迟函数,它会挂起。我实际上是在这里使用延迟函数模拟挂起函数网络调用。在这种情况下,如何从 awaitBlockingExample 获得结果?我清楚地明白,通过将 awaitBlockingExample 作为挂起函数,我可以完成这项工作并删除异步并在内部运行阻塞调用。但是这里 awaitBlockingExample (非挂起函数)代表了一个由这个方库提供的实现,它在我们的实现中被覆盖。比如guava缓存提供了reload函数,我想重写reload函数(非suspend函数),并从reload方法中调用协程函数来刷新数据库或网络调用的缓存值。

【问题讨论】:

  • 你使用的是哪个 kotlin 版本?

标签: kotlin vert.x kotlin-coroutines vertx-verticle


【解决方案1】:

适用于 Kotlin 1.3.0 及更高版本

private val mainScope = CoroutineScope(Dispatchers.Main)

fun start(){
        mainScope.launch { 
            val data = withContext(Dispatchers.IO){
                //This function will return the result. Return type of the below function will be type of data variable above.
                awaitBlockingExample()
            }
            //use your data result from async call. Result will be available here as soon as awaitBlockingExample() return it.
        }
        //Your function will continue execution without waiting for async call to finish.
    }

 fun awaitBlockingExample():String {
    //Your Logic
   }

希望这会有所帮助。

【讨论】:

    【解决方案2】:

    尝试下一个方法:

    override fun start() {
        GlobalScope.launch {
            val result = awaitBlockingExample()
        }
    }
    
    suspend fun awaitBlockingExample(): String {
        val response =  makeSuspendFunCall()
        println(" The final Result is $response")
        return response
    }
    
    suspend fun makeSuspendFunCall():String{
        println("Comming here 3")
        return suspendCoroutine {
            delay(500)
            val resp="Test"
            it.resume(resp)
        }
    }
    

    【讨论】:

      【解决方案3】:

      问题在于vertx.dispatcher() 使用单个线程作为事件循环,而runBlocking 阻塞了该线程。

      详情:

      您的 awaitBlockingExample() 函数正在此 Vertx 事件循环线程上运行,因为它是从 suspend start() 函数触发的。如果你调用runBlocking(),这个 Vertx 线程被阻塞并且永远不会被释放。但是你的其他协程,例如async(),现在没有线程可以做他们的工作。

      解决办法:

      我假设从start 函数调用awaitBlockingExample 仅在此示例中发生。实际上,我会假设外部回调使用自己的线程。那么就完全没有问题了,因为现在外线程被阻塞了:

      override suspend fun start() {
      
          //simulate own thread for external callback
          thread {
              awaitBlockingExample()
          }
      }
      
      fun awaitBlockingExample():String {
      
          val future= async(vertx.dispatcher()) {
      
              makeSuspendFunCall()
          }
          val result:String= runBlocking(vertx.dispatcher()){future.await()}
          println(" The final Result is $result")
          return result
      }
      

      顺便说一句:你不需要async()块,你可以直接从runBlocking()调用makeSuspendFunCall()

      fun awaitBlockingExample():String = runBlocking(vertx.dispatcher()){
          val result = makeSuspendFunCall()
          println(" The final Result is $result")
          result
      }
      

      【讨论】:

      • 正如我在问题中提到的,我特别在寻找一种解决方案,说明如何从非挂起函数调用协程 IO 阻塞调用。我只是在代码 sn-p 中简化了我的用例。比如guava缓存提供了reload函数,我想重写reload函数(非挂起函数),并从reload方法中调用协程函数来刷新数据库或网络调用的缓存值。我们也不能使用suspendCoroutine,因为reload方法不是suspend函数,在suspendCoroutine里面我们不能调用协程函数
      • 从未挂起的函数调用挂起函数总是阻塞的。你可以使用runBlocking。
      • @sedovav 如果您看到我在我的问题中使用了运行阻塞,当我从非挂起函数调用挂起函数并调用挂起函数时,我的问题是调用另一个挂起函数(在问题延迟函数),整个线程被阻塞,应用程序永远不会恢复
      • @DevLoper 我已经改变了我的答案,希望现在更好。
      • @Rene 答案现在正在解决我讨论过的问题,但并没有完全解决它。如果我使用您的解决方案,我将在线程“main”io.vertx.core.VertxException 中遇到错误异常:从错误的线程执行。未找到 ForkJoinPool.commonPool-worker-1 的上下文。这是因为suspend函数使用vertx提供的httpclient,默认dispatcher没有vertx上下文。
      猜你喜欢
      • 2018-12-29
      • 1970-01-01
      • 1970-01-01
      • 2013-11-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-13
      • 2021-05-17
      相关资源
      最近更新 更多