我有一個使用 Spring WebFlux 和 Kotlin 實作的 Rest API,其端點用于啟動長時間運行的計算。因為讓呼叫者等到計算完成并不是很優雅,所以它應該立即回傳一個 ID,呼叫者可以使用該 ID 在不同的端點上獲取結果,一旦它可用。計算在后臺開始,只要準備好就應該完成 - 我并不真正關心它何時完成,因為輪詢它是呼叫者的作業。
當我使用 Kotlin 時,我認為解決這個問題的規范方法是使用協程。這是我的實作方式的一個最小示例(使用Spring 的 Kotlin DSL而不是傳統控制器):
import org.springframework.web.reactive.function.server.coRouter
// ...
fun route() = coRouter {
POST("/big-computation") { request: ServerRequest ->
val params = request.awaitBody<LongRunningComputationParams>()
val runId = GlobalResultStorage.prepareRun(params);
coroutineScope {
launch(Dispatchers.Default) {
GlobalResultStorage.addResult(runId, longRunningComputation(params))
}
}
ok().bodyValueAndAwait(runId)
}
}
但是,這并沒有達到我想要的效果,因為外部協程( 之后的塊POST("/big-computation"))一直等到其內部協程完成執行,因此只有runId在不再需要它之后才回傳。
我能找到的唯一可行的方法是使用GlobalScope.launch,它會產生一個沒有父級等待其結果的協程,但我到處都讀到,強烈建議您不要使用它。為了清楚起見,有效的代碼如下所示:
POST("/big-computation") { request: ServerRequest ->
val params = request.awaitBody<LongRunningComputationParams>()
val runId = GlobalResultStorage.prepareRun(params);
GlobalScope.launch {
GlobalResultStorage.addResult(runId, longRunningComputation(params))
}
ok().bodyValueAndAwait(runId)
}
我是否遺漏了一些非常明顯的東西,這些東西會使我的示例使用適當的結構化并發作業,或者這真的是一個合法的用例GlobalScope嗎?有沒有辦法在不附加到它啟動的范圍內啟動長時間運行計算的協程?我能想到的唯一想法是從同一個 coroutineScope 啟動計算和請求處理程式,但由于計算取決于請求處理程式,我不知道這怎么可能。
非常感謝!
uj5u.com熱心網友回復:
也許其他人不會同意我的看法,但我認為這整個反感GlobalScope有點夸張。我經常有這樣的印象,有些人并不真正理解問題出在哪里GlobalScope,他們用具有相似缺點或實際上相同的解決方案來代替它。但是好吧,至少他們不再使用邪惡GlobalScope了......
不要誤會我的意思:GlobalScope不好。特別是因為它太容易使用,所以很容易過度使用它。但是有很多情況我們并不真正關心它的缺點。
結構化并發的主要目標是:
- 自動等待子任務,所以我們不會在子任務完成之前意外地繼續。
- 取消個別作業。
- 取消/關閉調度后臺任務的服務/組件。
- 在異步任務之間傳播失敗。
這些特性對于提供可靠的并發應用程式至關重要,但在很多情況下,它們都無關緊要。讓我們舉個例子:如果您的請求處理程式在應用程式的整個時間內都在作業,那么您不需要等待子任務和關閉功能。您不想傳播失敗。取消單個子任務在這里并不真正適用,因為無論我們使用GlobalScope還是“適當”的解決方案,我們都完全相同 - 通過將任務存盤在Job某處。
因此,我會說不GlobalScope鼓勵的主要原因不適用于您的情況。
話雖如此,我仍然認為可能值得實施通常被建議作為GlobalScope. 只需使用您自己的屬性創建一個屬性CoroutineScope并使用它來啟動協同程式:
private val scope = CoroutineScope(Dispatchers.Default)
fun route() = coRouter {
POST("/big-computation") { request: ServerRequest ->
...
scope.launch {
GlobalResultStorage.addResult(runId, longRunningComputation(params))
}
...
}
}
你不會從中得到太多。它不會幫助您解決資源泄漏問題,也不會使您的代碼更可靠或其他什么。但至少它有助于以某種方式對后臺任務進行分類。在技??術上可以確定誰是后臺任務的所有者。您可以在一個地方輕松配置所有后臺任務,例如提供CoroutineName或切換到另一個執行緒池。您可以計算當前有多少活動子任務。如果需要,可以更輕松地添加優雅關機。等等。
但最重要的是:實施成本低。你不會得到太多,但也不會花費你太多,所以為什么不呢。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/322719.html
標籤:春天 科特林 弹簧-webflux kotlin 协程
