Reputation: 2579
I want to instantly respond to the client and handle the request in the background by launching a coroutine. First i tried the following solution:
suspend fun PipelineContext<Unit, ApplicationCall>.handleTest(value: Unit) {
// insert calls to suspending functions here
launch {
repeat(10000) {
println("Executing background task $it.")
delay(1000)
}
}
call.respond("Executing the task in background")
}
routing {
get("/test", PipelineContext<Unit, ApplicationCall>::handleTest)
}
This works as expected. It returns instantly and executes the background task. Though, IntelliJ IDE gives me the following warning:
Ambiguous coroutineContext due to CoroutineScope receiver of suspend function
I know what this warning means and why it occurs, so I tried to find a way around this:
suspend fun handleTest(context: PipelineContext<Unit, ApplicationCall>) {
// insert calls to suspending functions here
context.launch {
repeat(10000) {
println("Executing background task $it.")
delay(1000)
}
}
context.call.respond("Executing the task in background")
}
routing {
get("/test") {
handleTest(this)
}
}
This piece of code also works as expected, however it looks wrong to me when reading this article, https://elizarov.medium.com/explicit-concurrency-67a8e8fd9b25. The author explains that you should not launch coroutines inside a suspending function unless wrapping it in a new coroutineScope {}
.
I was curious and tried out to inline handleTest
:
routing {
get("/test") {
// insert calls to suspending functions here
launch {
repeat(10000) {
println("Executing background task $it.")
delay(1000)
}
}
call.respond("Executing the task in background")
}
}
This also works as expected, and even the warning is gone. However, the construct is theoretically still the same as in the first solution.
What is the correct solution to my problem?
Upvotes: 1
Views: 4844
Reputation: 57
You need to use withContext() with suspend functions, otherwise they would not suspend. Suspend is not same as blocking(where threads block). You need to use something similar to below:
suspend fun handleTest(context: PipelineContext<Unit, ApplicationCall>) withContext(Dispatchers.Default) {
repeat(10000) {
println("Executing background task $it.")
delay(1000)
}
context.call.respond("Executing the task in background")
}
Blocking and Suspend for more details on blocking vs suspend
Upvotes: 1
Reputation: 398
You can use CoroutineScope:
routing {
get("/test") {
// insert calls to suspending functions here
CoroutineScope(Job()).launch {
repeat(10000) {
println("Executing background task $it.")
delay(1000)
}
}
call.respond("Executing the task in background")
}
It will create a new scope decouple the handler context.
Upvotes: 4
Reputation: 6999
I suggest creating a new coroutine scope to make it clear from where the coroutineContext
is taken because both CoroutineScope
and suspend function have it.
suspend fun PipelineContext<Unit, ApplicationCall>.handleTest(value: Unit) = coroutineScope {
// insert calls to suspending functions here
launch {
repeat(10000) {
println("Executing background task $it.")
delay(1000)
}
}
call.respond("Executing the task in background")
}
Upvotes: 3