我正在嘗試實作功能:我有一個休息端點,它呼叫執行可能需要很多時間的代碼。我現在改進體驗的想法是將那段代碼包裝為一個新執行緒,等待完成或等待最長時間過去并回傳適當的訊息。即使端點已經發回訊息,也應該完成包裝代碼。當前的實作如下所示:
private const val N = 1000
private const val MAX_WAIT_TIME = 5000
@RestController
@RequestMapping("/long")
class SomeController(
val service: SomeService,
) {
private val executor = Executors.newFixedThreadPool(N)
@PostMapping
fun longEndpoint(@RequestParam("someParam") someParam: Long): ResponseEntity<String> {
val submit = executor.submit {
service.longExecution(someParam)
}
val start = System.currentTimeMillis()
while (System.currentTimeMillis() - start < MAX_WAIT_TIME) {
if (submit.isDone)
return ResponseEntity.ok("Done")
}
return ResponseEntity.ok("Check later")
}
}
第一個問題是-等待時間似乎不對,我們不釋放執行緒,是否可以改進?
更重要的問題 - 如何將其重寫為 Kotlin 協程?我的嘗試很簡單,任務完成后不回傳,看起來像這樣:
@PostMapping
fun longEndpoint(@RequestParam("someParam") someParam: Long): ResponseEntity<String> = runBlocking {
val result = async {
withContext(Dispatchers.Default) {
service.longExecution(someParam)
}
}
delay(MAX_WAIT_TIME)
return@runBlocking ResponseEntity.ok(if(result.isCompleted) "Done" else "Check later")
}
但即使通過回傳正確的字串,答案也不會longExecution在完成之前發送。如何解決這個問題,我錯過了什么?也許協程在這里是不好的應用程式?
uj5u.com熱心網友回復:
您當前的協程嘗試存在幾個問題:
- 您正在的范圍
async內啟動計算runBlocking,因此整體端點方法將等待子協程完成,盡管您在此return之前嘗試-ing。 delay()MAX_WAIT_TIME即使任務完成得更快,也將始終等待- (可選)
runBlocking如果您的框架支持異步控制器方法,則根本不必使用(Spring WebFlux 確實支持suspend控制器中的功能)
對于第一個問題,請記住,每次啟動一個應該比您的函式壽命更長的協程時,您都必須使用外部作用域。coroutineScope或者runBlocking在這些情況下不合適,因為它們會等待您的子協程完成。
您可以使用CoroutineScope()工廠函式來創建作用域,但您需要考慮協程的生命周期以及何時取消它。如果longExecution函式有錯誤并且永遠掛起,你不想泄漏呼叫它的協程并炸毀你的記憶體,所以你應該以某種方式取消這些協程。這就是為什么您應該將范圍作為變數存盤在您的類中并在適當的時候取消它(當您想放棄這些操作時)。
對于第二個問題,使用withTimeout很常見,但它不適合您的用例,因為您希望任務即使在等待超時后也能繼續進行。一種可能的解決方案是使用select子句等待作業完成,或者等待某個指定的最長時間:
// TODO call scope.cancel() somewhere appropriate (when this component is not needed anymore)
val scope = CoroutineScope(Job())
@PostMapping
fun longEndpoint(@RequestParam("someParam") someParam: Long): ResponseEntity<String> {
val job = scope.launch {
longExecution()
}
val resultText = runBlocking {
select {
job.onJoin() { "Done" }
onTimeout(MAX_WAIT_TIME) { "Check later" }
}
}
return ResponseEntity.ok(resultText)
}
注意:我使用launch而不是async因為你似乎不需要longExecution這里的回傳值。
如果你想解決的問題#3太大,你可以簡單地宣布你的處理程式suspend,并洗掉runBlocking周圍的select:
// TODO call scope.cancel() somewhere appropriate (when this component is not needed anymore)
val scope = CoroutineScope(Job())
@PostMapping
suspend fun longEndpoint(@RequestParam("someParam") someParam: Long): ResponseEntity<String> {
val job = scope.launch {
longExecution()
}
val resultText = select {
job.onJoin() { "Done" }
onTimeout(MAX_WAIT_TIME) { "Check later" }
}
return ResponseEntity.ok(resultText)
}
請注意,這需要spring-boot-starter-webflux代替spring-boot-starter-web。
uj5u.com熱心網友回復:
您的實作總是等待 MAX_WAIT_TIME。這可能有效:
@PostMapping
fun longEndpoint(@RequestParam("someParam") someParam: Long): ResponseEntity<String> = runBlocking {
try {
withTimeout(MAX_WAIT_TIME) {
async {
withContext(Dispatchers.Default) {
service.longExecution(someParam)
}
}
}
} catch (ex: CancellationException) {
return@runBlocking ResponseEntity.ok("Check later")
}
return@runBlocking ResponseEntity.ok("Done")
}
雖然我不確定是否會有任何不需要的副作用,因為這似乎會在達到 MAX_WAIT_TIME 時取消協程。在此處閱讀更多相關資訊:
取消和超時
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/399187.html
上一篇:無法從配置服務器加載配置屬性源
