Table of content
- CoroutineContext - Explain
- CoroutineScope
- Coroutine Dispatcher
- What is Context Switching
- CoroutineScope vs CoroutineContext
CoroutineContext - Explain
- The CoroutineContext provides the context in which a coroutine runs, influencing its behavior and execution characteristics.
- The Coroutine context is at the core of every coroutine. Every coroutine is lied to a specific coroutine context.
A coroutine context is represented by the CoroutineContext
interface. The CoroutineScope
interface inherits from CoroutineContext and is commonly used to create and manage coroutines.
Most important CoroutineContext elements are :-
-
Dispatcher(CoroutineDispatcher) - Specifies the thread or threads on which the coroutine runs. Common dispatchers include Dispatchers.Default for CPU-intensive work, Dispatchers.IO for I/O-bound work, and Dispatchers.Main for updating the UI.
-
Job - Represents the lifecycle of the coroutine and allows to control the execution of the coroutine, cancel it, and check its status.
-
Error handler (CoroutineExceptionHandler) - Handles uncaught exceptions that occur in the coroutine. It allows you to define a custom handler for exceptions.
-
CoroutineName - Provides a name for the coroutine, which can be useful for debugging and logging purposes.
-
CoroutineScope - Defines the scope of a coroutine and is a special case of CoroutineContext. It provides a convenient way to launch coroutines.
CoroutineScope
- CoroutineScope is a convenient way to manage the lifecycle of coroutines within a specific context (typically associated with an Android component such as an Activity or Fragment).
- A CoroutineScope provides a structured way to launch and manage coroutines, and it ensures that all coroutines launched within that scope are cancelled when the associated Android component is destroyed.
- This helps prevent memory leaks and ensures that no dangling coroutines continue to execute after the UI component is no longer active.
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.*
class MainViewModel : ViewModel() {
// CoroutineScope associated with the ViewModel
private val viewModelScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
fun performBackgroundTask() {
viewModelScope.launch {
// Coroutine code
val result = fetchDataFromNetwork()
updateUi(result)
}
}
private suspend fun fetchDataFromNetwork(): String {
// Simulate a network request
delay(1000)
return "Data from network"
}
private fun updateUi(result: String) {
// Update the UI on the main thread
println("Updating UI with result: $result")
}
override fun onCleared() {
super.onCleared()
// Cancel all coroutines when the ViewModel is cleared (e.g., when the associated UI component is destroyed)
viewModelScope.cancel()
}
}
In the above code snippet:
- viewModelScope is an instance of CoroutineScope associated with the Dispatchers.Main dispatcher.
- This means that coroutines launched within this scope will execute on the main thread, making it safe to update the UI.
- viewModelScope.launch is used to launch a coroutine within the ViewModel scope.
- onCleared is an override method of ViewModel where we cancel the viewModelScope.
- Cancelling the scope is important to ensure that any ongoing coroutines associated with the ViewModel are cancelled when the ViewModel is no longer needed (e.g., when the associated UI component is destroyed).
Coroutine Dispatcher
- A coroutine dispatcher is responsible for defining the execution context in which a coroutine runs.
- It determines the thread or threads on which the coroutine’s code is executed.
- Dispatchers play a crucial role in managing concurrency and are used to control the thread pool or thread that a coroutine runs on.
Several built-in dispatchers are present in kotlin Coroutine, each serving a specific purpose:
-
Default (Dispatchers.Default):
- It is optimized for CPU-intensive work.
- It is backed by a shared pool of threads.
-
IO (Dispatchers.IO):
- It is optimized for I/O-intensive work, such as network or disk operations.
- It uses a larger pool of threads compared to the default dispatcher.
-
Main (Dispatchers.Main):
- It is designed for UI-related work in Android applications.
- It is typically used for updating the user interface.
- It is available through the kotlinx-coroutines-android library in Android.
-
Unconfined (Dispatchers.Unconfined):
- It executes the coroutine in the current thread until it is suspended and resumes it in the same thread.
- It is not suitable for CPU-intensive or long-running operations.
-
Custom Dispatchers:
- One can create custom dispatchers using
newSingleThreadContext
ornewFixedThreadPoolContext
functions, allowing to specify the characteristics of the thread pool.
- One can create custom dispatchers using
What is Context Switching
- Context switching is a concept where the state of a process or thread is saved, and the execution context is switched to another process or thread. This allows multiple processes or threads to share the same CPU core or cores efficiently, enabling concurrent execution.
- When the operating system decides to switch from executing one task to another, it performs a context switch.
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
class MainActivity : AppCompatActivity() {
private val job = Job()
private val uiScope: CoroutineScope = CoroutineScope(Dispatchers.Main + job)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Perform background task without blocking the UI thread
uiScope.launch {
showLoading()
// Switch to the background thread (IO dispatcher)
val result = withContext(Dispatchers.IO) {
// Simulate a background task (e.g., network request)
delay(2000)
"Background task completed"
}
// Switch back to the main thread to update the UI
updateUi(result)
hideLoading()
}
}
private fun showLoading() {
// UI code on the main thread
textView.text = "Loading..."
}
private fun updateUi(result: String) {
// UI code on the main thread
textView.text = result
}
private fun hideLoading() {
// UI code on the main thread
progressBar.visibility = View.GONE
}
override fun onDestroy() {
super.onDestroy()
// Cancel the coroutine scope when the activity is destroyed
job.cancel()
}
}
In the above code snippet, demonstrates context switching between the main thread and a background thread, allowing you to perform background tasks without blocking the UI.
- The uiScope is a CoroutineScope associated with the main thread (Dispatchers.Main).
- In the onCreate method, a coroutine is launched using uiScope.launch.
- Inside the coroutine, showLoading() is called to update the UI on the main thread.
- The withContext(Dispatchers.IO) block switches to the background thread (IO dispatcher) to perform a simulated background task (e.g., network request).
- After the background task is completed, updateUi(result) is called to switch back to the main thread and update the UI.
- Finally, hideLoading() is called to hide the loading indicator on the main thread.
CoroutineScope vs CoroutineContext
- A coroutine scope has a single property i.e coroutine context.
- Every coroutine needs to run in a specific coroutine scope and several coroutines can be started in the same scope.
- If a coroutine is started and some child coroutines are also started in a certain scope, then their job objects will form a parent child hierarchy.
- Each of the coroutines in the hierarchy run in the same scope and by default, each coroutine inherits the context of the scope.
In summary,
- CoroutineScope is focused on providing a structured way to launch and manage coroutines within a specific context, usually associated with the lifecycle of an Android component.
- It includes a coroutine context as part of its definition.
- CoroutineContext is a more general interface that represents the context of a coroutine, including various elements that define its behavior.
Please get in touch with us for next part. To be continued..
Happy learning !!!