Kotlin Coroutine Scope: Возврат @ runBlocking проблема, если используется в конечной точке контроллера

Цель: я хочу закодировать конечную точку, которая потребляет другую конечную точку, используя преимущества легких сопрограмм, предполагая, что я кодирую легкий ассинхронный клиент конечной точки.

Моя предыстория: впервые пытаюсь использовать Kotlin Coroutine. Я изучал последние дни и ищу вокруг. Я нашел множество статей, объясняющих, как использовать Coroutine в Android, и несколько других, объясняющих, как использовать Coroutine в основной функции. К сожалению, я не нашел статей, объясняющих, как кодировать конечную точку контроллера с сопрограммами, и это вызвало в моей голове сигнал, если я делаю что-то не рекомендованное.

Текущая ситуация: я успешно создал несколько подходов с использованием сопрограмм, но мне интересно, какой из них наиболее подходит для традиционного GET. Вдобавок мне интересно, как правильно работать с Exception.

Главный вопрос: какой из приведенных ниже подходов рекомендуется и о каком исключительном лечении мне следует позаботиться?

Связанный вторичный вопрос: в чем разница между

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

Служба, используемая для вызова другой конечной точки отдыха

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


}

person Jim C    schedule 17.12.2020    source источник


Ответы (1)


Обычно вы не хотите использовать runBlocking в производственном коде за пределами основной функции точки входа и тестов. Об этом говорится в документации по runBlocking < / а>.

Для сопрограмм важно понимать разницу между блокировкой и приостановкой.

Блокировка

Код блокировки предотвращает продолжение всего потока. Помните, что сопрограммы предназначены для асинхронного программирования, а не для многопоточности. Поэтому предположим, что две или более сопрограмм могут работать в одном потоке. Что происходит, когда вы блокируете поток? Никто из них не может бежать.

Это опасно. Код блокировки может полностью испортить ваше асинхронное программирование. Иногда приходится использовать код блокировки, например, при работе с файлами. В Kotlin есть особые способы справиться с этим, например Диспетчер ввода-вывода, который будет запускать код в собственном изолированном потоке, чтобы он не мешал другим вашим сопрограммам.

Приостановка

Это сердце сопрограмм. Идея состоит в том, что, когда ваша сопрограмма приостановлена, она сообщает области сопрограммы, что в это время может выполняться другая сопрограмма. Подвешивающая часть полностью абстрагируется от того, как работают асинхронные механизмы. Эта часть зависит от реализации области действия и диспетчера.

Приостановка не происходит автоматически. Некоторые фреймворки, такие как KTor, используют сопрограммы в своем API, поэтому часто вы обнаружите, что функции приостанавливаются.

Если у вас есть длительные операции, которые по своей сути не приостанавливаются, вы можете преобразовать их, используя что-то вроде того, что я упоминал в разделе «Блокировка».

Так что лучше?

Ну, это зависит от этой строки:

demoService.fetchUrl()

fetchUrl() приостанавливает или блокирует? Если он блокирует, то все ваши предложения примерно одинаковы (и не рекомендуются). Если он приостанавливается, то лучше всего подходит третий вариант.

Если это блокирует, то лучший способ справиться с этим - создать область сопрограммы и обернуть ее чем-то, что заставляет ее приостанавливаться, например withContext и вернуть его из функции приостановки.

Однако это справедливо только в том случае, если эти функции вызываются из сопрограммы. Я не знаком с Микронавтом. Если эта структура автоматически вызывает ваши методы и не использует сопрограммы, то вообще нет смысла вводить их в этот класс.

person Clay07g    schedule 17.12.2020