`GlobalScope` <is getting guard rails>! My dream c...
# coroutines
z
GlobalScope
is getting guard rails! My dream come true 😌
🎉 2
r
I know
GlobalScope
usage is discouraged, but I think it's hard to avoid it when developing on Kotlin/JS. Most of the time there is just no better/easier way to launch coroutines. Of course I can create another singleton object instead, but for me it just doesn't make sense.
➕ 1
b
🤣 congrats! Honest question regarding
GlobalScope
, while I agree that it should never be used, would it be better to use a new scope that is never canceled for tasks that need to run once in a coroutine, or run it in the global scope? For example, on an Android project, I have a specific job I want to run once, and don't need any result back. It can take the entire app process lifecycle (if it is short) or run until completion.
I leaned toward a new scope at first, but then started leaning for using the global scope, since that new scope object was a one-shot object already. This task was a new thread beforehand, but now runs in the IO dispatcher (it's IO work), just to give some context.
z
Firstly, even if you are using
GlobalScope
, you shouldn’t hard-code it – you should inject it and provide it from a module near the root of your DI graph. Then, it’s still not a bad idea to make your own scope, because you can set the dispatcher explicitly, instead of having to remember what dispatcher
GlobalScope
happens to use.
☝️ 1
l
I have an
AppScope
in real world projects
➕ 2
j
Firstly, even if you are using 
GlobalScope
, you shouldn’t hard-code it – you should inject it and provide it from a module near the root of your DI graph.
Why so? Isn't injecting a dispatcher sufficient?
b
These are some good suggestions. I am iffy on adding GlobalScope to the DI graph, as I can see that being abused. We are pushing to ensure all scopes are created with the main dispatcher, so having one that isn't would break trust. Having an AppScope that follows our paradigm in the DI graph might be the better option, but then the same lifecycle problems with global scope would exist in this app scope... Might revisit some code tomorrow to see what would become our best practice for this.
l
If your
AppScope
has a
Job
inside, it's safer than
GlobalScope
.
☝️ 2
🙏 1
z
Yea, I wouldn’t use global scope even when it’s injected (your example, using the main dispatcher for all coroutines by default instead of Default, is a great reason), but the point is that if it is injected, none of your code actually has to know or care what that root coroutine scope actually is, and changing it to a different scope is trivial.
➕ 1
Why so? Isn’t injecting a dispatcher sufficient?
It’s better than nothing, but injecting a context/scope also lets you customize other things at the root. Eg: • In a lot of the main production apps I work on, we actually keep the process alive across independent instrumentation tests and for that to work you need everything that is scoped to the “app” to actually be disposable/cancellable. • Adding logging or debug interceptors
➕ 1
🙏 1
a
@Robert Jaros I am actively developing in Kotlin/JS (browser) and I never have to use
GlobalScope
at all, I just
Copy code
CoroutineScope(window.asCoroutineDispatcher()).launch {
  // do things here
}
I think there is a Kotlin/JS(node) way of getting a dispatcher scoped to the process
r
Is there any significant difference between using above and the
GlobalScope
on JS?
j
What does "safer" mean?
l
r
Do I understand correctly that we should always keep the reference to the CoroutineScope?
l
Yes
Top-level, or class instance scoped works.
r
So the way proposed by @andylamax (
CoroutineScope(window.asCoroutineDispatcher()).launch { }
) is still not safe enough?
l
You need to keep a reference to the
CoroutineScope
or to the
Job
returned by
launch
or it will be garbage collected whenever there's no strong reference held and the coroutine is suspended.
Usually, it doesn't happen because whatever callback is waiting keeps a strong reference to the continuation, but there are widely used callback APIs that use WeakReferences internally. For example. SharedPreferences and MediaPlayer on Android.
In those cases, no strong ref to your listener, which was the only one holding a ref to the continuation. If GC passes during that potentially very long timeframe, the coroutine goes away silently and you wonder why something is not happening or is not working.
Actually, I think you're making a case to submit a feature request for an IDE inspection that puts a red warning when you create a
CoroutineScope
but don't put it in a property.
➕ 1
Want to submit it on kotl.in/issue @Robert Jaros?
r
I think it should be more clearly stated in the documentation here: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/, because it doesn't mention the GC problem.
l
That'd be an issue to raise on GitHub
Probably there should be a troubleshooting specific page in the doc.
r
With
GlobalScope
"gone" 😉 the documentation on creating custom scopes the right way (with all this important information) should be somewhere in the main docs here https://github.com/Kotlin/kotlinx.coroutines
a
So, for
JS
something like
Copy code
val scope = CoroutineScope(window.asCoroutineDispatcher())

scope.launch {
  // do stuff here
}
works?
What a polite way to say, "I told you so"😂
😅 2
b
This thread has been great insight on some of the minor nuances of Coroutines, especially when it comes to using
GlobalScope
. Thanks @louiscad for the insight on the ticket and explanation in ensuring a strong reference to a job is kept.
🙂 2
Working with my team to update our best practice to avoid GlobalScope at all costs (instead of having really good reasons). Going with an
AppScope
object now.
👍 2
🙌 2
u
What about injecting scope vs. letting the class own its scope?
Copy code
@AppScope
1. class MessageSender() : HasLifecycle {
	private val scope = SuperviserScope(...)

	fun sendMessage() {
		scope.launch {
			doSendMessage()
		}
	}

	private suspend fun doSendMessage() {
		...
	}

	override fun destroy() {
		scope.cancel()
	}
}

@AppScope
2. class MessageSender(scope: coroutineScope) {

	fun sendMessage() {
		scope.launch {
			doSendMessage()
		}
	}

	private suspend fun doSendMessage() {
		...
	}
}
any reasons for one over the other?
l
It's not the topic of this thread FYI.