【问题标题】:Kotlin Coroutine Scope : Is return@runBlocking an issue if used in Controller EndpointKotlin Coroutine Scope:如果在控制器端点中使用,return@runBlocking 是否存在问题
【发布时间】:2020-12-17 20:59:11
【问题描述】:

目的:假设我正在编写一个轻型异步端点客户端,我想编写一个使用另一个端点的端点,该端点利用轻型协程。

我的背景:第一次尝试使用 Kotlin Coroutine。我最近几天学习并四处寻找。我发现很多文章解释了如何在 Android 中使用协程,我发现很少有其他文章解释如何在主函数中使用协程。不幸的是,我没有找到解释如何使用协程对控制器端点进行编码的文章,如果我做的事情不推荐,它就会在我脑海中响起。

当前情况:我使用 Coroutines 成功创建了一些方法,但我想知道哪种方法最适合传统的 GET。最重要的是,我想知道如何正确处理异常。

主要问题:推荐以下方法中的哪一种,我应该关注哪种异常处理?

相关次要问题:两者有什么区别

fun someMethodWithRunBlocking(): String? = runBlocking {
 return@runBlocking ...
}

suspend fun someMethodWithSuspendModifier(): String?{
  return ...
}

下面的所有暂定方法都在工作并返回 json 响应,但我不知道端点方法上的“runBlocking”和返回“return@runBlocking”是否会给我带来一些负面的缺点。

控制器(端点)

package com.tolearn.controller

import com.tolearn.service.DemoService
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Produces
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.*
import kotlinx.coroutines.runBlocking
import java.net.http.HttpResponse
import javax.inject.Inject

@Controller("/tolearn")
class DemoController {

    @Inject
    lateinit var demoService: DemoService

    //APPROACH 1:
    //EndPoint method with runBlocking CoroutineScope
    //Using Deferred.await
    //Using return@runBlocking
    @Get("/test1")
    @Produces(MediaType.TEXT_PLAIN)
    fun getWithRunBlockingAndDeferred(): String? = runBlocking {

        val jobDeferred: Deferred<String?> = async{
            demoService.fetchUrl()
        }

        jobDeferred.await()

        return@runBlocking jobDeferred.await()
    }

    //APPROACH 2:
    //EndPoint method with runBlocking CoroutineScope
    //Using return@runBlocking
    @Get("/test2")
    @Produces(MediaType.TEXT_PLAIN)
    fun getWithReturnRunBlocking(): String? = runBlocking {

        return@runBlocking demoService.fetchUrl()

    }

    //APPROACH 3:
    //EndPoint method with suspend modifier calling a suspend modifier function
    //No runBlocking neither CoroutineScope at all
    @Get("/test3")
    @Produces(MediaType.TEXT_PLAIN)
    suspend fun getSuspendFunction(): String? {

        return demoService.fetchUrlWithoutCoroutineScope()
    }
}

用于调用另一个 Rest Endpoint 的服务

package com.tolearn.service

import kotlinx.coroutines.coroutineScope
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.time.Duration
import javax.inject.Singleton

@Singleton
class DemoService {

    suspend fun fetchUrl(): String? = coroutineScope {

        val client: HttpClient = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)
                .followRedirects(HttpClient.Redirect.NEVER)
                .connectTimeout(Duration.ofSeconds(20))
                .build()

        val request = HttpRequest.newBuilder()
                .uri(URI.create("http://localhost:3000/employees"))
                .build()

        val response = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());

        print(response.get().body())

        return@coroutineScope response.get().body()
    }

    suspend fun fetchUrlWithoutCoroutineScope(): String? {

        val client: HttpClient = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)
                .followRedirects(HttpClient.Redirect.NEVER)
                .connectTimeout(Duration.ofSeconds(20))
                .build()

        val request = HttpRequest.newBuilder()
                .uri(URI.create("http://localhost:3000/employees"))
                .build()

        val response = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());

        return response.get().body()
    }

}

如果重要的话,这里是 build.gradle

plugins {
    id("org.jetbrains.kotlin.jvm") version "1.4.10"
    id("org.jetbrains.kotlin.kapt") version "1.4.10"
    id("org.jetbrains.kotlin.plugin.allopen") version "1.4.10"
    id("com.github.johnrengelman.shadow") version "6.1.0"
    id("io.micronaut.application") version "1.2.0"
}

version = "0.1"
group = "com.tolearn"

repositories {
    mavenCentral()
    jcenter()
}

micronaut {
    runtime("netty")
    testRuntime("junit5")
    processing {
        incremental(true)
        annotations("com.tolearn.*")
    }
}

dependencies {
    implementation("io.micronaut:micronaut-validation")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}")
    implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
    implementation("io.micronaut.kotlin:micronaut-kotlin-runtime")
    implementation("io.micronaut:micronaut-runtime")
    implementation("javax.annotation:javax.annotation-api")
    implementation("io.micronaut:micronaut-http-client")

    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.4.2")

    runtimeOnly("ch.qos.logback:logback-classic")
    runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin")
}


application {
    mainClass.set("com.tolearn.ApplicationKt")
}

java {
    sourceCompatibility = JavaVersion.toVersion("11")
}

tasks {
    compileKotlin {
        kotlinOptions {
            jvmTarget = "11"
        }
    }
    compileTestKotlin {
        kotlinOptions {
            jvmTarget = "11"
        }
    }


}

【问题讨论】:

    标签: kotlin kotlin-coroutines java-11 java-http-client


    【解决方案1】:

    您通常希望避免在“main”入口点函数和测试之外的生产代码中使用runBlocking。这在runBlocking documentation 中有说明。

    对于协程,了解阻塞挂起之间的区别很重要。

    阻止

    阻塞代码阻止整个线程继续。请记住,协程用于异步编程,而不是多线程。因此,假设两个或多个协程可以在同一个线程上运行。现在,当你阻塞线程时会发生什么?它们都不能运行。

    这很危险。阻塞代码绝对会毁掉你的异步编程。有时您必须使用阻塞代码,例如在处理文件时。 Kotlin 有特殊的方法来处理它,例如 IO Dispatcher,它将在自己的隔离线程上运行代码,这样它就不会破坏你的其他协程。

    暂停

    这是协程的核心。这个想法是,当你的协程被挂起时,它会告诉协程范围另一个协程可以同时执行。暂停部分完全抽象为异步机制的工作方式。那部分取决于作用域和调度程序的实现。

    暂停不会自动发生。一些框架,例如 KTor 在其 API 中使用协程,因此您经常会发现函数处于挂起状态。

    如果您有长期运行的操作,而这些操作并非固有地暂停,您可以使用我在“阻塞”部分中提到的方法来转换它们。

    那么有什么更好的呢?

    嗯,这取决于这一行:

    demoService.fetchUrl()
    

    fetchUrl() 是暂停还是阻塞?如果它被阻止,那么您的所有提案都大致相同(并且不推荐)。如果它正在暂停,那么您的第三个选项是最好的。

    如果它是阻塞的,那么处理它的最佳方法是创建一个协程作用域并将其包装在使其暂停的东西中,例如withContext,然后从您的暂停函数中返回它。

    但是,只有在协程中调用这些函数时才会出现这种情况。我对 Micronaut 不熟悉。如果那个框架自动调用你的方法而不使用协程,那么在这个类中引入它们根本没有意义。

    【讨论】:

      猜你喜欢
      • 2019-08-06
      • 1970-01-01
      • 2017-09-07
      • 2021-05-21
      • 2020-07-29
      • 2019-08-28
      • 1970-01-01
      • 1970-01-01
      • 2021-02-28
      相关资源
      最近更新 更多