简体   繁体   English

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

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

Purpose: I want to code an Endpoint which consume another Endpoint taking advantage of light Coroutines assuming I am coding a light assyncronous EndPoint client.目的:假设我正在编写一个轻量级异步端点客户端,我想编写一个使用轻量级协程的端点的端点。

My background: first time trying to use Kotlin Coroutine.我的背景:第一次尝试使用 Kotlin 协程。 I have studied last days and search around.我最近几天学习并四处寻找。 I found numerous article explaining how use Coroutine in Android and I found few others explaining how use Coroutine in a main function.我发现很多文章解释了如何在 Android 中使用协程,我发现很少有其他文章解释如何在主 function 中使用协程。 Unfortunatelly I didn't find articles explaining how code a Controller endpoint with coroutines and it ringed a bell in my mind if I am doing something not recommended.不幸的是,我没有找到解释如何使用协程对 Controller 端点进行编码的文章,如果我做的事情不推荐,它在我脑海中响起警钟。

Current situation: I successfully created few approaches using Coroutines but I am wondering which is the most appropriate for a traditional GET.当前情况:我使用 Coroutines 成功创建了一些方法,但我想知道哪种方法最适合传统的 GET。 On top of that, I am wondering how deal with Exception properly.最重要的是,我想知道如何正确处理异常。

Main question: which one from approaches bellow is recommended and which Exception treatment should I care of?主要问题:推荐以下方法中的哪一种,我应该关注哪种异常处理?

Related secondary question: what is the diference between相关的次要问题:有什么区别

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

and

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

All tentatives bellow are working and returning the json response but I don't know if "runBlocking" on endpoint method and returning "return@runBlocking" can cause me some negative drawback.下面的所有试探都在工作并返回 json 响应,但我不知道端点方法上的“runBlocking”和返回“return@runBlocking”是否会给我带来一些负面的缺点。

Controller (endpoint) 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()
    }
}

Service used to call another Rest Endpoint用于调用另一个 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()
    }

}

In case it matters, here are the build.gradle如果它很重要,这里是 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"
        }
    }


}

You generally want to avoid using runBlocking in production code outside of the "main" entry point function and tests.您通常希望避免在“主要”入口点 function 和测试之外的生产代码中使用runBlocking This is stated in the runBlocking documentation .这在runBlocking 文档中有说明。

For coroutines, it's important to understand the difference between blocking and suspending .对于协程,了解阻塞挂起之间的区别很重要。

Blocking阻塞

Blocking code prevents the entire thread from continuing.阻塞代码阻止整个线程继续。 Remember that coroutines are meant for asynchronous programming, not multi-threading.请记住,协程用于异步编程,而不是多线程。 Therefore, assume that two or more coroutines can run on the same thread.因此,假设两个或多个协程可以在同一个线程上运行。 Now, what happens when you block the thread?现在,当你阻塞线程时会发生什么? None of them can run.没有一个能跑。

This is dangerous.这是危险的。 Blocking code can absolutely ruin your asynchronous programming.阻塞代码绝对会毁掉你的异步编程。 Sometimes you have to use blocking code, such as when you work with files.有时您必须使用阻塞代码,例如在处理文件时。 Kotlin has special ways to deal with it, such as the IO Dispatcher , which will run the code on its own, isolated thread so that it doesn't disrupt your other coroutines. Kotlin 有特殊的方法来处理它,例如IO Dispatcher ,它将在自己的隔离线程上运行代码,这样它就不会破坏您的其他协程。

Suspending暂停

This is the heart of coroutines.这是协程的核心。 The idea is that when your coroutine is suspended, it tells the coroutine scope that another coroutine can execute in the meantime.这个想法是,当您的协程暂停时,它会告诉协程 scope 另一个协程可以同时执行。 The suspending piece is completely abstracted with how the asynchronous mechanisms work.暂停部分完全抽象为异步机制的工作方式。 That part is up to the implementation of the scope and dispatcher.这部分取决于 scope 和调度程序的实现。

Suspending doesn't happen automatically.暂停不会自动发生。 Some frameworks, like KTor use coroutines in their API, such that often you will find functions that are suspending.一些框架,如KTor ,在其 API 中使用协程,因此您经常会发现函数处于挂起状态。

If you have long-running operations that are not inherently suspending, you can convert them using something like what I mentioned in the "Blocking" section.如果您有长期运行的操作,这些操作本质上不是暂停,您可以使用我在“阻塞”部分中提到的方法来转换它们。

So What's Better?那么有什么更好的呢?

Well, that depends on this line:好吧,这取决于这一行:

demoService.fetchUrl()

Is fetchUrl() suspending or blocking? fetchUrl()是暂停还是阻塞? If it's blocking, then all of your proposals are roughly the same (and not recommended).如果它被阻止,那么您的所有提案都大致相同(并且不推荐)。 If it's suspending, then your third option is best.如果它正在暂停,那么您的第三个选项是最好的。

If it's blocking, then the best way to deal with it is to create a coroutine scope and wrap it in something that makes it suspending, such aswithContext , and return that from your suspending function.如果它是阻塞的,那么处理它的最佳方法是创建一个协程 scope 并将其包装在使其暂停的东西中,例如withContext ,然后从暂停的 function 中返回它。

However , this is only the case if these functions are being called from within a coroutine.但是,只有在协程中调用这些函数时才会出现这种情况。 I'm not familiar with Micronaut.我对 Micronaut 不熟悉。 If that framework is automatically calling your methods and not using coroutines, then there is no point introducing them in this class at all.如果该框架自动调用您的方法而不使用协程,那么在此 class 中引入它们根本没有意义。

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

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