[英]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.