import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.runBlocking
val numbers: StateFlow<Int> = (1..100).asFlow()
.onEach { delay(100) }
.let {
runBlocking {
it.stateIn(this)
}
}
fun main(){
println("hello")
println(numbers.value)
println("bye")
}
I expect that main function finish only in 100ms (wait for first emission)
but second print happens when all items emitted (takes about 100*100 ms) and also prints last item instead of first one!
am I missing something or it is a bug ?
That’s expected behaviour when you use runBlocking, because runBlocking won’t return until all of its child jobs have completed.
In other words, the numbers property won’t return a value until the entire flow has terminated. This is because calling stateIn(scope) launches a job inside the scope that collects all the items from the flow. You can see where this happens in the source code.
If you want to allow the numbers flow to emit values concurrently with your main function, you'll need to use a scope in which both functions can run together. For example, you could call runBlocking in the main function, and pass the scope as a receiver for the numbers property:
val CoroutineScope.numbers: StateFlow<Int> get() = (1..100).asFlow()
.onEach { delay(100) }
.let {
it.stateIn(this)
}
fun main() = runBlocking {
println("hello")
println(numbers.value)
println("bye")
}
Related
I have a lifecycle aware fragment scope coroutine function that checks whether a value received from the fragments' parent activity has a certain value. If the value is null a function is called that has viewModelScope.launch coroutine scope to start a count down before showing a dialog to inform the user that the value disables certain app functionalities.
The problem is that the viewModelScope.launch coroutine function is called all the time even though the conditional if statement is not true.My question is why would a viewModelScope coroutine function be called if it is inside a conditional that is clearly false? I did notice that if I Log an output inside the if conditional it is not logged and if I Log output outside the viewModelScope.launch coroutine it is also not called. So the scoped code runs notwithstanding the value of the conditional.
The workaround for this was to make the viewmodel function a suspend function and remove the viewModelScope.launch coroutine. But why would a function be called that does not meet a conditional. Does coroutines transcend the boundaries of logic?
The lifecycleScope function has the following make up:
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
viewModel.status.collectLatest {
binding.contentScanner.tvScannerStatus.text = it
if (statusCheck(it) == null) {
viewModel.reactToInactiveScanner(it) // This function is called even though the condition is false
}
}
}
}
The viewModelScope coroutine:
fun reactToInactiveScanner(s: String) {
viewModelScope.launch {
for(i in 1..5) {
if(isScannerUnavailable(s)) break
delay(1000L)
}
_scannerActive.value = isScannerUnavailable(s)
}
}
Probably statusCheck(it) is null when you think it is not ooor, since reactToInactiveScanner(s: String) is launching a coroutine in the view model scope and suspending for at least 5 seconds, and given that the viewmodel survives configuration change, doesnt matter what the lifecycle is doing, the coroutine in the viewmodel scope will keep running for 5 seconds.
try to make the function suspending :
suspend fun reactToInactiveScanner(s: String) {
for(i in 1..5) {
if(isScannerUnavailable(s)) break
delay(1000L)
}
_scannerActive.value = isScannerUnavailable(s)
}
and launch it in the lifecycle scope, so when the lifecycle stop the coroutine get canncelled and when it starts the coroutine gets launched again
I use livedata that collect item data, after it in onClicked() I get this data from livedata. What could be better approach for this?
lifecycleScope.launch {
lifecycleScope.async {
viewModel.fetchItem(args.workId)
}.await()
onClicked()
}
variables in viewModel
val item = _item.immutable()
private val _item = MutableLiveData<ViewState<Item?>>()
[...]
// I wait here for data from first code than I collect it because item is liveData as above
private fun onClicked() {
val item = viewModel.item.value?.dataOrNull
[...]
fetchItem method
fun fetchItem(id: Int) {
viewModelScope.launch {
_item.postAsyncValue { repository.getItem(id) }
}
}
Currently, I think your code is not doing what you think it does. fetchItem is not a suspend function. It launches a coroutine and immediately returns without waiting for the coroutine to finish. So, your async coroutine that calls it doesn't wait for that result either. There's no point in using async at all here since the code returns almost immediately.
I'm guessing that what you're trying to accomplish is wait for postAsyncValue to finish setting that new value. To do this, you need to make fetchItem a suspend function that waits for its work to be done.
I am not familiar with this postAsyncValue, but my best guess is that it is a helper extension function on MutableLiveData that takes a suspend lambda, calls it, and then sets the value to the LiveData on the main thread. If this is the case, you should just do this instead so the function actually waits for the task to be done before returning:
suspend fun fetchItem(id: Int) = withContext(Dispatchers.Main) {
_item.value = repository.getItem(id) // assuming getItem is a suspend function
}
//...
lifecycleScope.launch {
viewModel.fetchItem(args.workId)
onClicked()
}
If this isn't right, please add your source code for postAsyncValue and let me know.
Regarding your literal question, instead of using async followed immediately by await, you can use withContext(Dispatchers.IO) { }, but you would only do this if you are calling blocking code. You only need async when you're working with parallelism, so the compiler warns you that you're doing something silly if you immediately call await() after async { }.
I was just checking the behaviour of the Coroutine Dispatchers. So I was trying to understand how many threads each of these Dispatchers can create. To check this, I created a loop statement counting from 1 to 1_000_000.
In each loop iteration, I'm creating a coroutine with Dispatchers.IO, But when I see the output, it is not created 1_000_000 coroutines, and all these are DefaultDispatcher-worker threads, and it always stops execution in the range 40000-50000 randomly.
But when I replaced Dispatchers.IO with Dispatchers.Unconfined, it actually created all the 1_000_000 coroutines without fail and all these are created on Main thread.
So I need some help here to understand why Dispatchers.IO is failed in this case.
Dispatchers.IO:
fun main() {
println("Start of main")
val handler = CoroutineExceptionHandler { _, e -> e.printStackTrace() }
runBlocking {
for (i in 1..1000000) {
CoroutineScope(Dispatchers.IO + CoroutineName("Dispatchers.IO")).launch(handler) {
println("${Thread.currentThread().name} task number is :$i")
}
}
}
println("End of main")
}
Dispatchers.Unconfined
fun main() {
println("Start of main")
val handler = CoroutineExceptionHandler { _, e ->
e.printStackTrace()
}
runBlocking {
for (i in 1..1000000) {
CoroutineScope(Dispatchers.Unconfined + CoroutineName("Dispatchers.Unconfined")).launch(handler) {
println("${Thread.currentThread().name} task number is :$i")
}
}
}
println("End of main")
}
Thanks in advance
But when I see the output, it is not created 1_000_000 coroutines, and
all these are DefaultDispatcher-worker threads, and it always stops
execution in the range 40000-50000 randomly.
The reason for this, I believe is that you're not waiting for all child coroutines to complete, and your program exits prematurely.
In the below code, Job instances are collected in a list named jobs. After the loop, jobs.joinAll() causes the execution to wait for all child coroutines to complete.
What is interesting, is the difference in execution time Dispatchers.IO vs. Dispatchers.Unconfined.
Dispatchers.IO: Elapsed time 4.040274590s
Dispatchers.Unconfined: Elapsed time 959.173375ms
When adding the suspending delay(1) to the loop, Dispatchers.Unconfined will change thread from main to kotlinx.coroutines.DefaultExecutor.
Dispatchers.Unconfined executes in strict sequence 1..1_000_000.
Coroutines basics
Tested on AMD 8-core, Ubuntu 20.04.
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime
#OptIn(ExperimentalTime::class)
fun main() {
println("Start of main")
measureTime {
runBlocking {
val jobs = mutableListOf<Job>()
repeat(1_000_000) { index ->
jobs.add(launch(Dispatchers.IO) {
println("${Thread.currentThread().name} task number is :${index + 1}")
})
}
/** wait until child coroutines complete */
jobs.joinAll()
println("End of main")
}
}.also { duration -> println("Elapsed time: $duration") }
}
I have an instance of CoroutineScope and log() function which look like the following:
private val scope = CoroutineScope(Dispatchers.IO)
fun log(message: String) = scope.launch { // launching a coroutine
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // some blocking operation
}
And I use this test code to launch coroutines:
repeat(5) { item ->
log("Log $item")
}
The log() function can be called from any place, in any Thread, but not from a coroutine.
After a couple of tests I can see not sequential result like the following:
Log 0
Log 2
Log 4
Log 1
Log 3
There can be different order of printed logs. If I understand correctly the execution of coroutines doesn't guarantee to be sequential. What it means is that a coroutine for item 2 can be launched before the coroutine for item 0.
I want that coroutines were launched sequentially for each item and "some blocking operation" would execute sequentially, to always achieve next logs:
Log 0
Log 1
Log 2
Log 3
Log 4
Is there a way to make launching coroutines sequential? Or maybe there are other ways to achieve what I want?
Thanks in advance for any help!
One possible strategy is to use a Channel to join the launched jobs in order. You need to launch the jobs lazily so they don't start until join is called on them. trySend always succeeds when the Channel has unlimited capacity. You need to use trySend so it can be called from outside a coroutine.
private val lazyJobChannel = Channel<Job>(capacity = Channel.UNLIMITED).apply {
scope.launch {
consumeEach { it.join() }
}
}
fun log(message: String) {
lazyJobChannel.trySend(
scope.launch(start = CoroutineStart.LAZY) {
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // some blocking operation
}
)
}
Since Flows are sequential we can use MutableSharedFlow to collect and handle data sequentially:
class Info {
// make sure replay(in case some jobs were emitted before sharedFlow is being collected and could be lost)
// and extraBufferCapacity are large enough to handle all the jobs.
// In case some jobs are lost try to increase either of the values.
private val sharedFlow = MutableSharedFlow<String>(replay = 10, extraBufferCapacity = 10)
private val scope = CoroutineScope(Dispatchers.IO)
init {
sharedFlow.onEach { message ->
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // some blocking or suspend operation
}.launchIn(scope)
}
fun log(message: String) {
sharedFlow.tryEmit(message)
}
}
fun test() {
val info = Info()
repeat(10) { item ->
info.log("Log $item")
}
}
It always prints the logs in the correct order:
Log 0
Log 1
Log 2
...
Log 9
It works for all cases, but need to be sure there are enough elements set to replay and extraBufferCapacity parameters of MutableSharedFlow to handle all items.
Another approach is
Using Dispatchers.IO.limitedParallelism(1) as a context for the CoroutineScope. It makes coroutines run sequentially if they don't contain calls to suspend functions and launched from the same Thread, e.g. Main Thread. So this solution works only with blocking (not suspend) operation inside launch coroutine builder:
private val scope = CoroutineScope(Dispatchers.IO.limitedParallelism(1))
fun log(message: String) = scope.launch { // launching a coroutine from the same Thread, e.g. Main Thread
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // only blocking operation, not `suspend` operation
}
It turns out that the single thread dispatcher is a FIFO executor. So limiting the CoroutineScope execution to one thread solves the problem.
I have an instance of CoroutineScope and log() function which look like the following:
private val scope = CoroutineScope(Dispatchers.IO)
fun log(message: String) = scope.launch { // launching a coroutine
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // some blocking operation
}
And I use this test code to launch coroutines:
repeat(5) { item ->
log("Log $item")
}
The log() function can be called from any place, in any Thread, but not from a coroutine.
After a couple of tests I can see not sequential result like the following:
Log 0
Log 2
Log 4
Log 1
Log 3
There can be different order of printed logs. If I understand correctly the execution of coroutines doesn't guarantee to be sequential. What it means is that a coroutine for item 2 can be launched before the coroutine for item 0.
I want that coroutines were launched sequentially for each item and "some blocking operation" would execute sequentially, to always achieve next logs:
Log 0
Log 1
Log 2
Log 3
Log 4
Is there a way to make launching coroutines sequential? Or maybe there are other ways to achieve what I want?
Thanks in advance for any help!
One possible strategy is to use a Channel to join the launched jobs in order. You need to launch the jobs lazily so they don't start until join is called on them. trySend always succeeds when the Channel has unlimited capacity. You need to use trySend so it can be called from outside a coroutine.
private val lazyJobChannel = Channel<Job>(capacity = Channel.UNLIMITED).apply {
scope.launch {
consumeEach { it.join() }
}
}
fun log(message: String) {
lazyJobChannel.trySend(
scope.launch(start = CoroutineStart.LAZY) {
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // some blocking operation
}
)
}
Since Flows are sequential we can use MutableSharedFlow to collect and handle data sequentially:
class Info {
// make sure replay(in case some jobs were emitted before sharedFlow is being collected and could be lost)
// and extraBufferCapacity are large enough to handle all the jobs.
// In case some jobs are lost try to increase either of the values.
private val sharedFlow = MutableSharedFlow<String>(replay = 10, extraBufferCapacity = 10)
private val scope = CoroutineScope(Dispatchers.IO)
init {
sharedFlow.onEach { message ->
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // some blocking or suspend operation
}.launchIn(scope)
}
fun log(message: String) {
sharedFlow.tryEmit(message)
}
}
fun test() {
val info = Info()
repeat(10) { item ->
info.log("Log $item")
}
}
It always prints the logs in the correct order:
Log 0
Log 1
Log 2
...
Log 9
It works for all cases, but need to be sure there are enough elements set to replay and extraBufferCapacity parameters of MutableSharedFlow to handle all items.
Another approach is
Using Dispatchers.IO.limitedParallelism(1) as a context for the CoroutineScope. It makes coroutines run sequentially if they don't contain calls to suspend functions and launched from the same Thread, e.g. Main Thread. So this solution works only with blocking (not suspend) operation inside launch coroutine builder:
private val scope = CoroutineScope(Dispatchers.IO.limitedParallelism(1))
fun log(message: String) = scope.launch { // launching a coroutine from the same Thread, e.g. Main Thread
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // only blocking operation, not `suspend` operation
}
It turns out that the single thread dispatcher is a FIFO executor. So limiting the CoroutineScope execution to one thread solves the problem.