0

I’m currently learning about Coroutines. I’am following JetBrains’ hands-on: Intro to coroutines and channels. In Structured concurrency section, they mentioned this:

It’s possible to create a new scope without starting a new coroutine, by using the coroutineScope function. To start new coroutines in a structured way inside a suspend function without access to the outer scope, you can create a new coroutine scope that automatically becomes a child of the outer scope that this suspend function is called from.

Let’s say from inside a CoroutineScope, I’am calling a function loadUsers. These 3 implementations gave me the same result :

import kotlinx.coroutines.coroutineScope

suspend fun loadUsers(): List<User> {
    coroutineScope {
        //...
    }
}
suspend fun loadUsers(): List<User> {
    CoroutineScope(Dispatchers.Default).run  {
        //...
    }
}
suspend fun CoroutineScope.loadUsers(): List<User> {
    //...
}

Note: In the body I'am launching multiple coroutines. The full function code could be found in the Hands-on (modified in the question for simplicity).

Could someone answer these 2 questions :

  • What’s the difference between the 3 implementations ?
  • Are they some use cases that are requiring coroutineScope (lowel case C) and can’t be done without it ?

Thanks in advance.

I have seen this already: Difference between CoroutineScope and coroutineScope in Kotlin, but as I mentioned, the 3 styles gave me the same result, so I'm still confused about it.

Ismaïl
  • 60
  • 1
  • 7
  • What's the commented body here? Without launching any coroutines, you won't see the difference. Also `run` is not a coroutine builder, you're just using the stdlib's `run` function here, not launching a coroutine – Joffrey Mar 25 '23 at 13:34
  • Yes, I'am launching multiple coroutines inside the body, I'll modify the post to include this, and yes, `run` is a scope function, but what I meant is I got the same output with **CouroutineScope**, so why they included **cotoutineScope** – Ismaïl Mar 25 '23 at 13:50
  • This definitely doesn't give the same result if you launch coroutines. How are you measuring this? There are important differences like the fact that `coroutineScope` will wait for child coroutines to finish, whereas launching coroutines in a new unbounded scope like this will leak them without waiting. – Joffrey Mar 25 '23 at 13:59
  • It's the example from the Hands-on Here the exact [function](https://github.com/kotlin-hands-on/intro-coroutines/blob/solutions/src/tasks/Request5Concurrent.kt) that I mentioned in the question as **loadUsers** for simplicity Which is called from https://github.com/kotlin-hands-on/intro-coroutines/blob/solutions/src/contributors/Contributors.kt#L81 – Ismaïl Mar 25 '23 at 14:07
  • Changing the function to the styles that I mentioned gives the same result: when the users are loaded the UI is updated – Ismaïl Mar 25 '23 at 14:11
  • 1 is generally the correct style. – Louis Wasserman Mar 25 '23 at 17:23

1 Answers1

1

3 launches separate coroutines that are not child coroutines. You pretty much should never do this in a suspend function because it forfeits structured concurrency, makes the coroutine context ambiguous within the function, and your function isn’t suspending anyway. I can’t think of any exceptions where it would make sense to want a CoroutineScope argument in a suspend function.

2 is like 3, but even worse because you create a CoroutineScope that is not managed. The coroutine you are launching is not a child or a sibling. It cannot be cancelled under any circumstances. Creating an orphan CoroutineScope like this only to launch a coroutine and then forgetting about it is no different than simply using GlobalScope, which is highly discouraged except when you want to start a task that cannot be cancelled which should be extremely rare. And again, even if you are doing this, it shouldn't be done in a suspend function.

1 is the proper way to launch child coroutines and preserve structured concurrency. The suspend function will actually suspend to await all the child coroutines to finish instead of firing them off asynchronously like in 2 and 3.

3 would make sense for non-suspend functions that are to be treated as asynchronous functions. That said, after using coroutines for a few years, I haven’t ever found a need for a function like that. Seems a little clumsy to me since the behavior is slightly ambiguous or it’s at least a pattern not often used, but it could just be my opinion.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • You said 1 and 3 are the same thing except for syntax differences. I guess something wrong here, maybe you thought in case 1 that **coroutineScope** is an argument. In fact it is the [function](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html) from `kotlinx.coroutines.coroutineScope` – Ismaïl Mar 25 '23 at 18:18
  • Sorry, I was remembering code from a similar question I was looking at earlier today. I revised the answer. – Tenfour04 Mar 25 '23 at 21:27
  • I got it, almost all of it.. I'm still missing how we could say that a function "isn’t suspending anyway" even without seeing the full implementation, I mean, what if inside the body ( the `//...` part) I called another suspending function, this doesn't make the outer function suspending ? – Ismaïl Mar 26 '23 at 07:18
  • I’m just going off what you posted. It’s true, you could both suspend to do one thing in the current coroutine while also launching an unrelated coroutine to do a second thing. Personally I would never put two completely disconnected tasks into one function like that. – Tenfour04 Mar 26 '23 at 12:25
  • One thing your sample code lacks is an explanation of how you get the resulting List out of those other coroutines so you can return it in the current suspend function. – Tenfour04 Mar 26 '23 at 12:27
  • This is how we get the resulting List https://github.com/kotlin-hands-on/intro-coroutines/blob/solutions/src/tasks/Request5Concurrent.kt – Ismaïl Mar 26 '23 at 14:51
  • Can u please explain this also "makes the coroutine context ambiguous within the function" ? – Ismaïl Mar 26 '23 at 14:52
  • Normally, you can control what CoroutineContext a suspend function will use, at least for its non-lambda-nested code (code not in `withContext` or similar), but if you provide a CoroutineScope parameter, then it becomes ambiguous to the caller which context is used for which piece of the task. – Tenfour04 Mar 26 '23 at 16:40
  • Could u please answer [this](https://stackoverflow.com/q/75842925/9296194) also, u did already here, but it's slightly different and maybe I could learn something new there. – Ismaïl Mar 26 '23 at 23:49
  • I can't spot the difference except that you aren't returning anything from the sample functions like you are in this question (although in this question you left out how you were getting the response to return). Your linked code shows it with the `coroutineScope { }` way which is correct, but it's not so easy to get those results to return when you do it one of the other bad ways. – Tenfour04 Mar 27 '23 at 00:10