Why these two Kotlin coroutines functions are not the same? - kotlin

Main function:
fun main() = runBlocking {
val channel = Channel<Int>()
foo(channel)
for (y in channel) println(y)
println("Done!")
}
This works:
fun CoroutineScope.foo(channel: Channel<Int>) {
launch {
for (x in 1..5) channel.send(x * x)
channel.close()
}
}
This does not work (it blocks on channel send):
suspend fun foo(channel: Channel<Int>) = coroutineScope {
launch {
for (x in 1..5) channel.send(x * x)
channel.close()
}
}
Why?

As stated in the documentation of coroutineScope:
This function returns as soon as the given block and all its children coroutines are completed
So, writing coroutineScope { launch { something() }} is equivalent to just something(), because it waits for the launch's completion. This, in turn, means that your second version of foo returns only when the loop is completed and the channel closed.
On the other hand, using just launch launches an async task, so the code after foo() can work concurrently with it.
Actually, if you inline foo() in both cases, you will realise that the launch-ed coroutine is delimited by runBlocking's scope in the first case, and by coroutineScope's scope in the second case.

Related

How to implement timeout without interrupting a calculation in Kotlin coroutine?

Let's say a request started a long calculation, but ready to wait no longer than X seconds for the result. But instead of interrupting the calculation I would like it to continue in parallel until completion.
The first condition ("wait no longer than") is satisfied by withTimeoutOrNull function.
What is the standard (idiomatic Kotlin) way to continue the computation and execute some final action when the result is ready?
Example:
fun longCalculation(key: Int): String { /* 2..10 seconds */}
// ----------------------------
cache = Cache<Int, String>()
suspend fun getValue(key: Int) = coroutineScope {
val value: String? = softTimeoutOrNull(
timeout = 5.seconds(),
calculation = { longCalculation() },
finalAction = { v -> cache.put(key, v) }
)
// return calculated value or null
}
This is a niche enough case that I don't think there's a consensus on an idiomatic way to do it.
Since you want the work to continue in the background even if the current coroutine is resuming, you need a separate CoroutineScope to launch that background work, rather than using the coroutineScope builder that launches the coroutine as a child coroutine of the current one. That outer scope will determine the lifetime of the coroutines that it launches (cancelling them if it is cancelled). Typically, if you're using coroutines, you already have a scope on hand that's associated with the lifecycle of the current class.
I think this would do what you're describing. The current coroutine can wrap a Deferred.await() call in withTimeoutOrNull to see if it can wait for the other coroutine (that's not a child coroutine since it was launched directly from an outer CoroutineScope) without interfering with it.
suspend fun getValue(key: Int): String? {
val deferred = someScope.async {
longCalculation(key)
.also { cache.put(key, it) }
}
return withTimeoutOrNull(5000) { deferred.await() }
}
Here's a generalized version:
/**
* Launches a coroutine in the specified [scope] to perform the given [calculation]
* and [finalAction] with that calculation's result. Returns the result of the
* calculation if it is available within [timeout].
*/
suspend fun <T> softTimeoutOrNull(
scope: CoroutineScope,
timeout: Duration,
calculation: suspend () -> T,
finalAction: suspend (T) -> Unit = { }
): T? {
val deferred = scope.async {
calculation().also { finalAction(it) }
}
return withTimeoutOrNull(timeout) { deferred.await() }
}

In Kotlin, is it possible to substitute a suspend fun with a non-suspend version, without breaking the caller?

I'm learning concurrency in Kotlin, coming from C#/JavaScript background, and I can't help comparing some concepts.
In C# and JavaScript, technically we can rewrite an async function as a regular non-async version doing the same thing, using Task.ContinueWith or Promise.then etc.
The caller of the function wouldn't even notice the difference (I ranted about it in a blog post).
Is something like that possible for a suspend function in Kotlin (i.e., without changing the calling code)? I don't think it is, but I thought I'd still ask.
The closest thing I could come up with is below (Kotlin playground link), I still have to call .await():
import kotlinx.coroutines.*
suspend fun suspendableDelay(ms: Long): Long {
delay(ms);
return ms;
}
fun regularDelay(ms: Long): Deferred<Long> {
val d = CompletableDeferred<Long>()
GlobalScope.async { delay(ms); d.complete(ms) }
return d;
}
suspend fun test(ms: Long): Long {
delay(ms);
return ms;
}
fun main() {
val r1 = runBlocking { suspendableDelay(250) }
println("suspendableDelay ended: $r1");
val r2 = runBlocking { regularDelay(500).await() }
println("regularDelay ended: $r2");
}
https://pl.kotl.in/_AmzanwcB
If you're on JVM 8 or higher, you can make a function that calls the suspend function in an async job and returns a CompletableFuture, which can be used to get your result with a callback (thenApplyAsync()) or synchronously (get()).
val scope = CoroutineScope(SupervisorJob())
suspend fun foo(): Int {
delay(500)
return Random.nextInt(10)
}
fun fooAsync(): CompletableFuture<Int> = scope.async { foo() }.asCompletableFuture()
fun main() {
fooAsync()
.thenApplyAsync { println(it) }
Thread.sleep(1000)
}
The above requires the kotlinx-coroutines-jdk8 library.
I don't know of a solution that works across multiple platforms.
This can only work if you change your suspending function to a non-suspending blocking function, for example
private fun method(){
GlobalScope.launch {
val value = getInt()
}
}
// Calling coroutine can be suspended and resumed when result is ready
private suspend fun getInt(): Int{
delay(2000) // or some suspending IO call
return 5;
}
// Calling coroutine can't be suspended, it will have to wait (block)
private fun getInt(): Int{
Thread.sleep(2000) // some blocking IO
return 5;
}
Here you can simply use the non-suspending version, without any change on the caller.
But the issue here is that without suspend modifier the function becomes blocking and as such it can not cause the coroutine to suspend, basically throwing away the advantage of using coroutiens.

Why does a normal function need to be wrapped with viewModelScope.launch?

The following code is from the project.
1: In my mind,a suspend fun should be launched in another suspend fun or viewModelScope.launch{ }, withContext{ } ... , filterItems() is only a normal function, I don't know why filterItems() need to be wrapped with viewModelScope.launch{ } in the function filterTasks(), could you tell me ?
2: In the function filterTasks(), viewModelScope.launch{ } will launch in coroutines, it's asynchronous, I think return result maybe be launched before I get the result from viewModelScope.launch{}, so the result maybe null, is the code correct?
Code
private fun filterTasks(tasksResult: Result<List<Task>>): LiveData<List<Task>> {
val result = MutableLiveData<List<Task>>()
if (tasksResult is Success) {
isDataLoadingError.value = false
viewModelScope.launch {
result.value = filterItems(tasksResult.data, getSavedFilterType())
//return filterItems(tasksResult.data, getSavedFilterType()) //It will cause error.
}
} else {
result.value = emptyList()
showSnackbarMessage(R.string.loading_tasks_error)
isDataLoadingError.value = true
}
return result //I think it maybe be launched before I get the result from viewModelScope.launch{}
}
private fun filterItems(tasks: List<Task>, filteringType: TasksFilterType): List<Task> {
val tasksToShow = ArrayList<Task>()
// We filter the tasks based on the requestType
for (task in tasks) {
when (filteringType) {
ALL_TASKS -> tasksToShow.add(task)
ACTIVE_TASKS -> if (task.isActive) {
tasksToShow.add(task)
}
COMPLETED_TASKS -> if (task.isCompleted) {
tasksToShow.add(task)
}
}
}
return tasksToShow
}
It doesn't, unless it performs some heavy work and you want to move it to a background thread, which is the case here. Here the author just wanted to disjoint the work so the live data can be updated with an empty list first, and the filtered list later(computationally intensive to get), but forgot to do it out of the main thread.
In this particular case the author may have forgotten to add a background dispatcher as a parameter
viewModelScope.launch(Dispatchers.Default)
hence, in this scenario the intended behavior was not achieved, so you see this "nonsensical" coroutine.
I think you can contribute to the project with a fix :)
yes, you are right. but if you looked up the implementation of the launch {} such in lifecycleScope.launch {} or viewModelScope.launch {} you would find out the "block" which is "the coroutine code which will be invoked in the context of the provided scope" is cast to be suspend, so any block of code between launch {} is suspend code block. so in your example filterItems is cast to suspend under the hood and it's wrapped with viewModelScope.launch{ } to do its heavy task not in main thread.
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
// the below line is doing the magic
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
I agree that the code looks suspicious, main reason is that it launches the filterItems coroutine into the Main dispatcher, basically just postponing the moment when filterItems will run on the GUI thread. If filterItems takes long to complete, it will block the GUI; if it doesn't take long, then why would you launch a concurrent coroutine in the first place?
Furthermore, on an architectural level, I don't see a reason why you'd have a function returning LiveData<List<Task>> when you can just have a suspend fun returning List<Task>.

Why can't I parallel operation when I use either delay() or yield() in Kotlin?

The Code A, Code B and Code C get the same result Result All.
I think the Code B or Code C should get the result Result MyThink because I have added either delay() or yield().
It seems that flow.collect {...} is a block function.
Code A
fun foo(): Flow<Int> = flow {
println("Flow started")
for (i in 1..3) {
delay(500)
emit(i)
}
}
fun main() = runBlocking<Unit> {
println("Calling foo...")
val flow = foo()
println("Calling collect...")
flow.collect { value ->run {
println(value)
}
}
println("Done")
}
Code B
fun foo(): Flow<Int> = flow {
println("Flow started")
for (i in 1..3) {
delay(500)
emit(i)
}
}
fun main() = runBlocking<Unit> {
println("Calling foo...")
val flow = foo()
println("Calling collect...")
flow.collect { value ->run {
println(value)
delay(200)
}
}
println("Done")
}
Code C
fun foo(): Flow<Int> = flow {
println("Flow started")
for (i in 1..3) {
delay(500)
emit(i)
}
}
fun main() = runBlocking<Unit> {
println("Calling foo...")
val flow = foo()
println("Calling collect...")
flow.collect { value ->run {
println(value)
yield()
}
}
println("Done")
}
Result All
Calling foo...
Calling collect...
Flow started
1
2
3
Done
Result MyThink
Calling foo...
Calling collect...
Flow started
1
Done
2
3
It seems that flow.collect {...} is a block function.
That's not true in a literal sense, but there really is behaviour here that you might phrase as "blocking".
collect is a suspending function, which will return only after it has collected all of the items in the Flow that it was called on. Whenever the Flow suspends (with delay or yield, for example), the collection of the Flow is also suspended. This is all happening in the same coroutine (started by runBlocking in this case) that's suspended together. The Flow yielding values and collect processing them will continue after the suspension is over. Finally, when everything's collected, collect will return, and any code you have after it in that same coroutine will run.
This is consistent with the idea that coroutines are sequential by default, i.e. everything is executed top-to-bottom in your code, in order. If you want concurrent behaviour, you have to explicitly opt into it (for example, by launching new coroutines within the current one, with launch, or async). So what you call "blocking" is really just sequential. The collect function does not work like registering a listener would with many other APIs.
To understand the basic idea behind Flow, and how collecting it works within the same coroutine, I always recommend this talk.
If you want to have similar behavior as in Rx
you can use onEach instead collect with launchIn(this)
flow.onEach {
print(it)
}.launchIn(this)
https://proandroiddev.com/from-rxjava-2-to-kotlin-flow-threading-8618867e1955

What are differents between foo().collect{...} and launch { foo().collect{...} }?

The Code A get the Result A, and the Code B get the Result B .
1: Is the fun foo() coroutines ? why doesn't it add keyword suspend before if it's a coroutines function?
2: In my mind, a coroutines function is fired using launch {...} or aync {...} if fun foo() is a coroutines function, why can the main() fun in Code B invoke it directly?
3: What are differents between Code A and Code B ?
Code A
fun main() = runBlocking<Unit> {
launch {
for (k in 1..3) {
println("I'm not blocked $k")
delay(100)
}
}
launch {
foo().collect { value -> println(value) }
}
}
fun foo(): Flow<Int> = flow {
for (i in 1..3) {
delay(100)
emit(i+5)
}
}
Result A
I'm not blocked 1
I'm not blocked 2
6
I'm not blocked 3
7
8
Code B
fun main() = runBlocking<Unit> {
launch {
for (k in 1..3) {
println("I'm not blocked $k")
delay(100)
}
}
foo().collect { value -> println(value) }
}
fun foo(): Flow<Int> = flow {
for (i in 1..3) {
delay(100)
emit(i+5)
}
}
Result B
I'm not blocked 1
6
I'm not blocked 2
7
I'm not blocked 3
8
The foo function is not a suspending function, so you can call it outside a coroutine scope. This function builds a flow and returns the flow, but the flow does not start.
The flow only starts when you call collect on it, and that is a suspending function, so calling collect must be done in a coroutine scope.
flows are cold, they only start emitting items when you call collect on them, so building the flow is not a suspending function because it's a fast operation.
1: Is the fun foo() coroutines ? why doesn't it add keyword suspend before if it's a coroutines function?
Actually foo() is a regular function that returns a Flow.
2: In my mind, a coroutines function is fired using launch {...} or aync {...} if fun foo() is a coroutines function, why can the main() fun in Code B invoke it directly?
Also using runBlocking{...}, runBlockingTest{...}, produce{...} and actor{...}.
It doesn't matter where you build/create/declare a Flow, what matters is where you call terminal operators (like collect) which are suspending functions and must be called inside a coroutine or inside another suspending function. Btw, you're running all the main method inside a coroutine since you're wrapping it inside a runBlocking block.
3: What are differents between Code A and Code B ?
None. You're just creating another coroutine on Code A which will also run on the main thread.