Study Kotlin Coroutine, part 6: Coroutine Scope

Tram Ho

1. CoroutineScope

Imagine, when you run 10 coroutine to perform 10 tasks in a certain activity. When that Activity is destroyed, the results of the above tasks are no longer needed. How to stop those 10 coroutines to avoid memory leaks. Of course, you can manually stop each coroutine with the cancel() function, but Kotlin Coroutines provides a guy who can manage the life cycle of all 10 coroutines: CoroutineScope

We can declare a coroutine scope by instantiating an instance of the CoroutineScope(context: CoroutineContext) class CoroutineScope(context: CoroutineContext) . CoroutineScope variables passed to CoroutineScope is used as the coroutine context for the entire coroutine launched in the same scope.

As above code, I launch 3 coroutine in the same scope using context including Dispatchers.IO . So all 3 coroutine run with Dispatchers.IO .

We can also initialize a scope with factory functions like MainScope() . MainScope uses Dispatchers.Main default so we don’t need to declare context for this scope anymore.

All of the coroutine builders that I have introduced in previous articles like launch { } or async { } are extension functions of CoroutineScope class. Therefore you cannot call launch { } and async { } outside of a CoroutineScope . Particularly runBlocking { } is not an extension function of CoroutineScope but it accepts CoroutineScope as a parameter passed so it can be called outside CoroutineScope . runBlocking { } itself runBlocking { } thanks to accepting CoroutineScope as a param, it creates a scope to be able to run the coroutine inside it. So keep in mind, can’t launch 1 coroutine if it doesn’t have scope. In other words, in addition to CoroutineScope , it cannot launch coroutine at all.

In addition to the above, there is another way to create a scope that can launch coroutines. It is inheriting the CoroutineScope class and you will need to override the coroutineContext variable again.

Or custom your own scope and use it to run coroutines.

2. Features of Coroutine Scope

CoroutineScope has the following characteristics to be remembered and careful when working with Coroutine

  • When a coroutine A is launched in CoroutineScope of another coroutine B, A is a child of B. The child coroutine will use the scope and context of the parent coroutine. If that child coroutine is declared in a separate scope with its own context, it will prefer to use that scope instead of its parent.
  • A parent coroutine is always waiting for all of its children to finish its task. For example:

Output:

We now have one more coroutine builder, coroutineScope { } . It also runs sequentially like runBlocking { } so, except that it is a suspend function so it can only be created inside another suspend function or in a coroutine scope.

Explain, first the code inside runBlocking is run sequentially from top to bottom. When it launches coroutine 1, in coroutine 1 there is a delay of 200ms but runBlocking will not wait to run down to launch coroutine 2. In coroutine 2 again launch a coroutine called coroutine 3. But in both coroutine 2 and 3 there is a delay. runBlocking should have run down the last line to print line 4, but no, it will be printed at the end. Because in the same scope created by runBlocking (scope 1), it must wait for all its children (coroutine 1,2 and 3) to finish running before it can run its code. So, line 3 code is delayed by at least 100ms so it should be printed first, followed by line 1 and line 2.

  • When the parent coroutine is destroyed, all of its children are also destroyed

Output:

Explain: after 100ms of delay, the line of code 1 will be printed, and then wait for 1000ms of more line code 2 to be printed. Meanwhile, after the first 500ms, the parent coroutine has been canceled so it will cancel all of its child coroutines. So line code 2 will never be printed.

3. GlobalScope.

Output:

We all know when canceling coroutine father, all coroutine children will be canceled. However, if the child coroutine has scope of GlobalScope , it will not be canceled when the parent coroutine is canceled. Therefore, line 1 code will still be printed despite being delayed for up to 1000ms.

4. Using Coroutine Scope to manage the application lifecycle

As I put the problem at the beginning of the article, all coroutines must be canceled when Activity , Fragment , ViewModel are canceled to avoid memory leaks. So we should use Coroutine Scope to manage the lifecycle of Activity , Fragment , ViewModel . Specifically: When the activity is destroyed, cancel the coroutine scope with the cancel() function cancel()

When the viewmodel is clear, cancel the coroutine scope

5.viewModelScope

When you add the following dependency to the project, there is a scope in viewModelScope in Dispatchers.Main in every ViewModel

Conclude

By the end of part 6, hope you understand Coroutine Scope. The next article I will introduce about handling Exception in coroutine. Thank you for following this article. Hope you will keep watching the sequel 😄

Reference source:

https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html#coroutine-scope

Read the previous sections:

Learn Kotlin Coroutine, part 1: Introduce Kotlin Coroutine and asynchronous programming techniques

Learn Kotlin Coroutine, part 2: Build first coroutine with Kotlin

Learn Kotlin Coroutine, part 3: Coroutine Context and Dispatcher

Study Kotlin Coroutine, part 4: Job, Join, Cancellation and Timeouts

Learn Kotlin Coroutine, part 5: Async & Await

Continue reading Part 7: Learning Kotlin Coroutine together, Part 7: Handling Exceptions in Coroutine, Supervision Job & Supervision Scope

Share the news now

Source : Viblo