繁体   English   中英

Kotlin 协程 Scope:如果在 Controller 端点中使用,是 return@runBlocking 问题

[英]Kotlin Coroutine Scope : Is return@runBlocking an issue if used in Controller Endpoint

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

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

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

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

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

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

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

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

Controller(端点)

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 端点的服务

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"
        }
    }


}

您通常希望避免在“主要”入口点 function 和测试之外的生产代码中使用runBlocking 这在runBlocking 文档中有说明。

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

阻塞

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

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

暂停

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

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

如果您有长期运行的操作,这些操作本质上不是暂停,您可以使用我在“阻塞”部分中提到的方法来转换它们。

那么有什么更好的呢?

好吧,这取决于这一行:

demoService.fetchUrl()

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM