Kotlin Training Program

DOWNLOAD APP

FEEDBACK

Scope Builders

Scope Builders are code constructs used to build CoroutineScope.

Scope builders are used to bridge the gap between non-coroutine world and coroutine world. We have been using a scope builder since we wrote our very first coroutine program i.e. the runBlocking() function. To launch coroutines inside main() function, CoroutineScope is required which is provided by the runBlocking() function as a lambda receiver.

 fun main = runBlocking { /* this: CoroutineScope */
		// launch coroutines here
}
 

runBlocking() isn’t the only scope builder, we have several more. Let’s get to know them one by one.

CoroutineScope constructor

We can use the CoroutineScope constructor to build a new scope. It compulsorily takes CoroutineContext as input parameter which can be used to specify context elements like Dispatcher and CoroutineExceptionHandler.

 CoroutineScope(/* CoroutineContext */)

// Example :
val ioScope = CoroutineScope(Dispatchers.IO)

ioScope.launch {
		// Coroutine world
}
 

GlobalScope

GlobalScope is singleton CoroutineScope available globally to directly launch coroutines. Example :

 GlobalScope.launch {
		// async code here
}
 

SupervisorScope

SupervisorScope is a special CoroutineScope, useful in coroutine exception handling. We’ll discuss it in detail under Exception Handling chapter.

SupervisorScope can be created using the supervisorScope() suspend function. It can only be invoked from CoroutineScope or a suspend function.

 fun main() = runBlocking { /* this: CoroutineScope */
		supervisorScope { /* this: CoroutineScope */
				// async code here...
		}
}

// OR

suspend fun task() {
		supervisorScope { /* this: CoroutineScope */
				// async code here...
		}
}
 

Because it is a suspend function, it returns only after all of its child coroutines have finished.

Revisiting runBlocking

Let’s write a simple async program using CoroutineScope constructor.

 fun main() {
    CoroutineScope(Dispatchers.Default).launch {
        println("Hello")
    }
}

// No Output
 

The program didn’t print anything even though we created a new scope and launched a coroutine. Why is this so? When using runBlocking, it works :

 fun main() {
    runBlocking {
        launch {
            println("Hello")
        }
    }
}

// Output : Hello
 

The reason behind this must be clear from the name itself - runBlocking. It runs the input suspend lambda by blocking the control flow. Only after all coroutines inside it have finished, it can return and resume the control flow i.e. it awaits the completion of all its coroutines. This isn’t the case with CoroutineScope builder. The constructor call returns a CoroutineScope object, which is then used to call launch() function, which is asynchronous and non-blocking function.

 runBlocking {
		// No need to call launch()
}

CoroutineScope(Dispatchers.Default).launch {
		// launch() is called
}
 

runBlocking is a special scope builder construct, different from all others. Instead of getting the scope object, we pass in the main coroutine lambda to it. runBlocking handles this root coroutine differently. It awaits the completion of all its child coroutines and then return.

In the following program, main() function returns before the coroutine finishes because launch() is a non-blocking function. Thus the program ends and we see no output.

 fun main() {
    CoroutineScope(Dispatchers.Default).launch {
        println("Hello")
    }
}
 

⚠️ When working with console applications, we’ll use runBlocking instead of any other scope builder. Other scope builders are used in UI applications where program end is triggered by user. Also, runBlocking shouldn’t be used in UI applications because it is a blocking function.