异步/等待Kotlin协程阻止代码

如何解决异步/等待Kotlin协程阻止代码

我正在使用没有响应式Web的Spring Boot。

我尝试使用Kotlin协程运行一些异步请求

    @GetMapping
    fun test(): Message {
        val restTemplate = RestTemplate()
        return runBlocking {
            val hello = async { hello(restTemplate) }
            val world = async { world(restTemplate) }
            Message("${hello.await()} ${world.await()}!")
        }
    }

    private suspend fun world(restTemplate: RestTemplate): String {
        logger.info("Getting WORLD")
        return restTemplate.getForEntity("http://localhost:9090/world",World::class.java).body!!.payload
    }

    private suspend fun hello(restTemplate: RestTemplate): String {
        logger.info("Getting HELLO")
        return restTemplate.getForEntity("http://localhost:9090/hello",Hello::class.java).body!!.payload
    }

但是此代码按顺序运行。

我该如何解决?

解决方法

TL;DRasync 与用于卸载阻塞 IO 的自定义 Dispatcher(例如 Dispatchers.IO)一起使用。

val hello = async(Dispatchers.IO) { hello(restTemplate) }
val world = async(Dispatchers.IO) { world(restTemplate) }

更新: 我在 Kotlin coroutines slack channel 中被告知,我可以使用 async(Dispatchers.IO) 而不是使用 async + withContext(Dispatchers.IO)

我采用了@Sergey Nikulitsa 代码并创建了一个扩展函数,该函数采用带有接收器的 lambda(类似于 async)来组合 asyncwithContext(Dispatches.IO)

import kotlinx.coroutines.*

fun <T> CoroutineScope.myRestTemplateAsync(
    start: CoroutineStart = CoroutineStart.DEFAULT,block: suspend CoroutineScope.() -> T
): Deferred<T> {

    return async(Dispatchers.IO,start) {
        block() 
    }
}

然后它可以像这样在你的代码中使用:


@GetMapping
fun test(): Message {
    val restTemplate = RestTemplate()
    return runBlocking {
        val hello = myRestTemplateAsync { hello(restTemplate) }
        val world = myRestTemplateAsync { world(restTemplate) }
        Message("${hello.await()} ${world.await()}!")
    }
}

private suspend fun world(restTemplate: RestTemplate): String {
    logger.info("Getting WORLD")
    return restTemplate.getForEntity("http://localhost:9090/world",World::class.java).body!!.payload
}

private suspend fun hello(restTemplate: RestTemplate): String {
    logger.info("Getting HELLO")
    return restTemplate.getForEntity("http://localhost:9090/hello",Hello::class.java).body!!.payload
} 

初步结果

此时,我只是在试验这个方法,我只使用 Spring WebMVC 和 RestTemplate 进行 5+ 次调用。

myRestTemplateAsync 扩展函数与同步对应函数相比,执行时间持续减少了 30% 到 50%

为什么这比使用 async { } 更有效?

特别是对于 RestTemplate,在 async {...} 中使用 coroutineScope 似乎没有什么区别,并且执行时间与同步代码相当。

此外,查看分析器中的线程,在单独使用 async 时没有创建“Dispatcher Workers”。这让我相信 RestTemplate 的每请求线程模型阻塞了整个线程。

当在 async 中指定新的调度程序时,它将协程(和函数 block)的执行转移到 Dispatchers.IO 线程池中的一个新线程。

在这种情况下,代码块应包含 RestTemplate 调用(单个调用)。据我所知,这可以防止 RestTemplate 阻塞原始上下文。

为什么要使用这种方法?

如果您一直在大型项目中使用 RestTemplate(每请求线程模型),那么仅将其替换为非阻塞客户端(如 WebClient)可能是一项艰巨的任务。这样,您就可以继续使用大部分代码,只需在代码中可以异步进行多次调用的区域添加 myRestTemplateAsync

如果您要开始一个新项目,请不要使用 RestTemplate。最好使用 WebFlux with coroutines in Kotlin as explained in this article

这是个好主意吗?

目前,我没有足够的信息来说明这一点。我希望进行更广泛的测试和评估:

  • 负载下的内存消耗
  • 负载下可能耗尽线程池
  • 异常是如何传播和处理的

如果您对为什么这可能是一个好主意或可能不是一个好主意有任何意见,请在下面发表。

,
  • runBlocking:旨在将常规的阻塞代码桥接到以挂起方式编写的库中,以用于主要功能和测试中。

  • 在这里,我们使用coroutineScope方法创建一个CoroutineScope。此功能设计用于并行分解工作。当此范围内的任何子协程失败时,该范围将失败,并且所有其他子级都将被取消。

  • 因为coroutineScope是暂停函数,所以我们将fun test()标记为suspend fun(只允许从协程或另一个暂停函数调用暂停函数)。通过使用CoroutineScope对象,我们可以调用asynclaunch来启动协程

  @GetMapping
  suspend fun test(): Message {
        val restTemplate = RestTemplate()
        return coroutineScope {
            val hello = async { hello(restTemplate) }
            val world = async { world(restTemplate) }
            Message("${hello.await()} ${world.await()}!")
        }
    }
,

也许根本原因是:

  • restTemplate使用java.io(不是java.nio)
  • restTemplate阻止当前线程,直到获得HTTP响应
  • 协程魔术在这种情况下不起作用

解决方案:

  • 使用使用java.nio的http客户端
,

该代码可以并行工作:

    @GetMapping
    fun test(): Message {
        val restTemplate = RestTemplate()
        return runBlocking {
            val hello = async { hello(restTemplate) }
            val world = async { world(restTemplate) }
            Message("${hello.await()} ${world.await()}!")
        }
    }

    private suspend fun world(restTemplate: RestTemplate): String {
        logger.info("Getting WORLD")
        return withContext(Dispatchers.IO) {
            restTemplate.getForEntity("http://localhost:9090/world",World::class.java).body!!.payload
        }
    }

    private suspend fun hello(restTemplate: RestTemplate): String {
        logger.info("Getting HELLO")
        return withContext(Dispatchers.IO) {
            restTemplate.getForEntity("http://localhost:9090/hello",Hello::class.java).body!!.payload
        }
    }

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-