What's the difference between CoroutineScope(dispatchers).launch{} and coroutineScope{ launch{}}?
Say I have the code below:
(you can go to Kotlin playground to run this snippet https://pl.kotl.in/U4eDY4uJt)
suspend fun perform(invokeAfterDelay: suspend () -> Unit) {
// not printing
CoroutineScope(Dispatchers.Default).launch {
delay(1000)
invokeAfterDelay()
}
// will print
coroutineScope {
launch {
delay(1000)
invokeAfterDelay()
}
}
}
fun printSomething() {
println("Counter")
}
fun main() {
runBlocking {
perform {
printSomething()
}
}
}
And as the comment stated, when using CoroutineScope().launch it won't invoke the print, however when using the other way, the code behaves as intended.
What's the difference?
Thanks.
Further question
New findings.
if I leave the perform function like this (without commenting out one of the coroutines)
suspend fun perform(invokeAfterDelay: suspend () -> Unit) {
CoroutineScope(Dispatchers.Default).launch {
delay(1000)
invokeAfterDelay()
}
coroutineScope {
launch {
delay(1000)
invokeAfterDelay()
}
}
}
then both of these coroutines will be executed 🤔Why?
CoroutineScope().launch {} and coroutineScope { launch{} } have almost nothing in common. The former just sets up an ad-hoc scope for launch to run against, completing immediately, and the latter is a suspendable function that ensures that all coroutines launched within it complete before it returns.
The snippet under your "Further question" is identical to the original one except for the deleted comments.
Whether or not the first coroutine prints anything is up to non-deterministic behavior: while perform is spending time within coroutineScope, awaiting for the completion of the inner launched coroutine, the first one may or may not complete itself. They have the same delay.
Related
fun test(coroutineScope: CoroutineScope){
coroutineScope.launch {
//FUNC 1
}
CoroutineScope(Dispatchers.XXX).launch {
//FUNC 2
}
}
FUNC 1 Not working but FUNC 2 is work well!
I don't know the difference between the two.
This is because coroutineScope is a function which returns whatever the lambda block returns. And since it is a function, you need to invoke it like coroutineScope(block = { ... }) or coroutineScope { ... }. While in your 2nd case, CoroutineScope(Dispatchers.XXX) returns a CoroutineScope and since launch is an extension function on CoroutineScope, it is valid.
The names are actually a bit confusing. coroutineScope is a function which takes a suspending lambda with CoroutineScope as the receiver while CoroutineScope(context) is a function which creates a CoroutineScope.
coroutineScope is generally used to capture the current coroutineContext inside a suspend function. For example,
suspend fun uploadTwoFiles() {
// Here we need to launch two coroutines in parallel,
// but since "launch" is an extension function on CoroutineScope,
// we need to get that CoroutineScope from somewhere.
coroutineScope {
// Here we have a CoroutineScope so we can call "launch"
launch { uploadFile1() }
launch { uploadFile2() }
}
// Also, note that coroutineScope finishes only when all the children coroutines have finished.
}
Using coroutineScope has this advantage that the coroutines launched inside will get cancelled when the parent coroutine cancels.
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>.
I'm learning Coroutines of Kotlin.
The Code A use async in the coroutines of Kotlin and I can use .await() on a deferred value to get its eventual result, so one.await() will return Int.
If I use launch in the coroutines, can I get the actual value just like one.await()?
Code A
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // pretend we are doing something useful here
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // pretend we are doing something useful here, too
return 29
}
There is no [standard non-hacky] way to get the result from launch. This is the whole point of having async.
The 2 functions launch and async have precisely one difference, which is that launch is fire-and-forget, and async allows to wait for a result.
Therefore, there should be no reason for you to want to use launch over async unless you don't need the result, so the question is quite suprising.
If you use launch, the "actual value" is Unit as you can see from the signature
fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job (source)
so you don't even need to start it.
If you pass a lambda to launch as in
launch { doSomethingUsefulOne() }
it is really
launch { doSomethingUsefulOne(); Unit }
and the value of doSomethingUsefulOne() is thrown away.
The short answer is NO
As you pointed out async and await will get you the result.
But Launch is used for a different purpose. Its purpose is to act as a bridge between Coroutine and NonCoroutine worlds.
Consider an example of a ViewModel whose coroutine world is controlled by viewModelScope
fun nonCoroutineWorldFunction() {
....
....
viewModelScope.launch {
// runs in coroutine world
}
....
....
viewModelScope.launch {
// runs in coroutine world
}
}
Launch can be considered something similar to FIRE AND FORGET. You just launch it to do its job rather than waiting for it to do its job
How to make multiple launch like multiple thread in kotlin
I want to make that first second are working at the same time forever!!
Like this code...
runBlocking {
// first
launch{
first()
}
// second
launch{
second()
}
}
suspend fun first(){
// do something
delay(1000L)
// Recursive call
first()
}
suspend fun second(){
// do something
delay(1000L)
// Recursive call
second()
}
Your example code would work already, if it is the only running code in your application. If you need those two methods running in parallel to your application, wrap them in GlobalScope.launch:
GlobalScope.launch {
launch { first() }
launch { second() }
}
This will run forever until canceled and/or an exception inside is thrown. If you don't require too many resources inside a coroutine and release them properly when used, you should never have a problem with StackOverFlow.
In addition to recursive code: try to make a loop instead as it is suggested in the comments.
You can implement infinite execution with cycles
runBlocking {
launch { while(true) first() }
launch { while(true) second() }
}
suspend fun first(){
// do something
delay(1000L)
}
suspend fun second(){
// do something
delay(1000L)
}
Given we have a suspending function but this is not a CoroutineScope, how can we launch other coroutines such that those are associated with the current scope of whatever runs this suspending function?
Every suspendable function has access to the global variable coroutineContext which you can trivially wrap in CoroutineScope, but that is not its intended purpose. It's there so you can check at any point whether your coroutine was cancelled, reach the debug info like the job name, etc.
In the words of Roman Elizarov in his recent Medium post:
suspend fun doNotDoThis() {
CoroutineScope(coroutineContext).launch {
println("I'm confused")
}
}
Do not do this!
A suspendable function should not fire off concurrent work that may go on after it returns. It should only use concurrency to achieve parallel decomposition of a task, and that means it will wait for all the child coroutines to complete.
You should decide to either use a plain function that is the receiver of CoroutineScope (signaling the intention to launch concurrent work) or use a suspendable function that awaits on the completion of all the work it initiated.
So, if you want parallel decomposition, then use a coroutineScope or, possibly a supervisorScope block:
coroutineScope {
launch {
// ... task to run in the background
}
// ... more work while the launched task runs in parallel
}
// All work done by the time we reach this line
coroutineScope is a suspendable function and it won't complete until all the coroutines it launched complete.
You can create an extension function on CoroutineScope or function with CoroutineScope as a parameter:
fun CoroutineScope.doThis() {
launch { ... }
}
fun doThatIn(scope: CoroutineScope) {
scope.launch { ... }
}
Also you can use coroutineScope or supervisorScope, depending on your needs:
suspend fun someFun() = coroutineScope {
launch { ... }
}
suspend fun someFun() = supervisorScope {
launch { ... }
}
You could just use withContext() or coroutineScope() for launching another coroutine:
withContext(coroutineContext) {
launch { ... }
}
While the second would override the Job of the context, but reuse the context:
coroutineScope {
launch { ... }
}