Difference between CoroutineScope and coroutineScope in Kotlin
Best difference between CoroutineScope
(Capital C version) vs coroutineScope
(Smaller c version), I could figure out and which was easily understandable was correlating them with Unstructured vs Structured concurrency
Let me share an example :
class MainActivity : AppCompatActivity() {
private lateinit var btn: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn = findViewById(R.id.start_btn)
btn.setOnClickListener {
CoroutineScope(Dispatchers.Main).launch {
val result = downloadUserData()
Toast.makeText(applicationContext, "Result : $result", Toast.LENGTH_LONG).show()
}
}
}
private suspend fun downloadUserData(): Int {
var result = 0
// Here, we use CoroutineScope (Capital C version) which will start a new scope and
// launch coroutine in new scope Dispatchers.IO, Not In Parent Scope which is Dispatchers.Main
// Thus, this function would directly return without waiting for loop completion and will return 0
CoroutineScope(Dispatchers.IO).launch {
for (i in 0 until 100) {
kotlinx.coroutines.delay(10)
result++
}
}
return result
}
}
Output :
Result : 0
This is an example of Unstructured Concurrency where it is not guaranteed that child coroutine would complete before returning. Thus, caller/parent coroutine would get wrong value returned by child coroutine. Even, when child coroutine has returned already, child coroutine may be running (in Active state) in the background which may lead to Memory Leaks in certain cases.
Solution :
When we need to communicate between multiple coroutines, we need to make sure Structured Concurrency (Recommended)
This can be done by re-using parent/caller coroutine scope inside child/callee coroutine. This can be achieved by coroutineScope {}
(Smaller c) version inside child/callee coroutine.
private suspend fun downloadUserData(): Int {
var result = 0
// By using coroutineScope (Smaller c version) below, we ensure that this coroutine would execute in the
// parent/caller coroutine's scope, so it would make sure that the for loop would complete
// before returning from this suspended function. This will return 20000 properly
coroutineScope {
for (i in 0 until 100) {
kotlinx.coroutines.delay(10)
result++
}
}
return result
}
Output : Result : 100
CoroutineScope()
is nothing but a factory of CoroutineScope
objects, and a CoroutineScope
object is nothing but a holder of a CoroutineContext
. It has no active role in coroutines, but it's an important part of the infrastructure that makes it easy to do structured concurrency properly. This comes from the fact that all coroutine builders like launch
or async
are extension functions on CoroutineScope
and inherit its context.
You will rarely, if ever, have the need to call CoroutineScope()
because usually you either pick up an existing coroutine scope or have one created for you by other convenience functions (like MainScope
on Android) or Kotlin internals.
coroutineScope()
, on the other hand, is a function that executes the block you pass it inside a sub-coroutine. It is basically an alias for withContext(this.coroutineContext)
and you should primarily use it when you want to launch one or more background coroutines while you continue some work in the foreground, and then join on the background coroutines when completing the block.