简体   繁体   中英

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. 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. 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.

Current situation: I successfully created few approaches using Coroutines but I am wondering which is the most appropriate for a traditional 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.

Controller (endpoint)

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

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

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. This is stated in the runBlocking documentation .

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.

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. The suspending piece is completely abstracted with how the asynchronous mechanisms work. That part is up to the implementation of the scope and dispatcher.

Suspending doesn't happen automatically. Some frameworks, like KTor use coroutines in their API, such that often you will find functions that are suspending.

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? 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.

However , this is only the case if these functions are being called from within a coroutine. I'm not familiar with 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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