Table of content
- Suspend function — explanation
- Threads Vs Coroutines in short
- Blocking Vs Suspending
- How delay() works
Suspend function — explanation
- One can mark functions with the suspend keyword to indicate that they can be paused and resumed. They are long running functions.
- These functions can be used within coroutines and are a central part of the coroutine ecosystem.
- Libraries like retrofit, room exposed to this suspend functions in their API to load data from the network or from the database.
- Can only be called from another suspend function or a coroutine.
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import retrofit2.Retrofit
import retrofit2.create
class WorkRepository {
private val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()
private val apiService = retrofit.create(ApiService::class.java)
suspend fun fetchData(): ResultType {
return withContext(Dispatchers.IO) {
try {
val result = apiService.getData()
// Process the result if needed
result
} catch (e: Exception) {
// Handle errors or throw a custom exception
throw CustomException("Failed to fetch data", e)
}
}
}
}
In this above code snippet
-
The fetchData function is marked as a suspend function, which allows it to be called from a coroutine without blocking the calling thread.
-
withContext(Dispatchers.IO) to switch the coroutine context to the I/O dispatcher, indicating that the network request should be performed on a background thread.
-
If an exception occurs during the network request, we catch it, handle errors, and optionally throw a custom exception.
class MainViewModel : ViewModel() {
private val workRepository = WorkRepository()
fun fetchData() {
viewModelScope.launch {
try {
val result = myRepository.fetchData()
// Update UI with the result
} catch (e: CustomException) {
// Handle custom exception
} catch (e: Exception) {
// Handle other exceptions
}
}
}
}
Using suspend functions with coroutines simplifies asynchronous programming.
Threads Vs Coroutines in short
So first we see about Threads
-
Concurrency Model: Threads provide a lower-level concurrency model, allowing to run multiple tasks concurrently.They are part of the Java language and can be used in Android for parallel execution.
-
Complexity: Working with threads can be complex due to the need for synchronization, handling race conditions, and managing thread pools. Incorrect usage can lead to issues such as deadlocks and data corruption.
-
Blocking Operations: Threads are suitable for blocking operations, such as network requests or disk I/O, without freezing the UI.
Thread {
// Code to run in the background
// ...
}.start()
Now going to the Coroutines
-
Concurrency Model: Coroutines provide a higher-level concurrency model and are part of Kotlin. They are designed to simplify asynchronous programming, making code more readable and maintainable.
-
Simplicity: Coroutines are simpler to use and manage compared to threads. They allow to write asynchronous code in a more sequential manner.
-
Cancellation: Coroutines come with built-in support for cancellation, making it easier to handle the lifecycle of asynchronous tasks.
-
Coroutines are non-blocking by nature, enabling to write asynchronous code without resorting to callbacks or complex threading constructs.
// Suspend function
suspend fun doBackgroundTask() {
// Code to run in the background
// ...
}
// Calling the suspend function from a coroutine
CoroutineScope(Dispatchers.Main).launch {
doBackgroundTask()
}
Blocking Vs Suspending
Blocking Threads
- refer to the situation where a thread is paused or waits for a particular operation to complete before proceeding with the next instructions.
- When a thread is blocked, it cannot execute other tasks. If multiple threads are blocked, it can lead to reduced concurrency and responsiveness.
- Commonly used in scenarios where synchronous (blocking) behavior is acceptable or necessary, such as reading from a file, making a network request, or waiting for a resource to become available.
fun main() {
println("main function starts")
threadRoutine(1, 500)
threadRoutine(2, 300)
Thread.sleep(1000)
println("main function ends")
}
fun threadRoutine(number: Int, delay: Long) {
thread{
println("Routine $number starts working")
Thread.sleep(delay)
println("Routine $number has finished")
}
}
In the above code snippet the main function calls the function called thread routine two times. For each invocation, a new thread is started in which work gets performed. This work is represented by a call to Thread.sleep. The main function sleeps for 1000 milliseconds to wait for the threads to complete.
Pictorial diagram of working of Blocking Thread
Suspending a Coroutine
- Suspending coroutines allow functions to be paused without blocking the underlying thread.
- Coroutines provide a more structured and sequential approach to concurrency. Coroutines can suspend without blocking the thread, allowing other tasks to execute in the meantime.
- When a coroutine suspends, the underlying thread can be freed to perform other tasks. This promotes efficient use of resources and can lead to higher concurrency.
- Ideal for handling asynchronous tasks, such as network requests, database queries, or any operation that may take time to complete.
fun main() = runBlocking {
println("main function starts")
joinAll{
async{suspendingCoroutine(1, 500)}.
async{suspendingCoroutine(2,300)}
async{
repeat(5){
println("other task is working on ${Thread.currentThread().name}")
delay(100)
}
}
}
println("main function ends")
}
suspend fun suspendingCoroutine(number: Int, delay: Long) {
println("Coroutine $number starts work on ${Thread.currentThread().name}")
delay(delay)
println("Coroutine $number has finished on ${Thread.currentThread().name}")
}
Output will look like of the above code snippet
Pictorial diagram of working of Suspend function
In modern Android development, coroutines are commonly used to handle asynchronous tasks due to their simplicity, readability, and ability to manage concurrency efficiently. They provide an alternative to traditional thread-based approaches, promoting more scalable and maintainable code.
How delay() works
- delay() is a suspending function, and when called within a coroutine, it doesn’t block the thread on which the coroutine is running.
- While the coroutine is in the suspended state during the delay, the underlying thread is free to execute other tasks or coroutines.
- delay() takes a single argument, which is the time to pause the coroutine. The time is specified in milliseconds by default.
- If the coroutine is cancelled before the delay completes, the delay() function throws a CancellationException. This allows for the clean cancellation of coroutines during their execution.
- The delay() function is typically used within a suspend function or a coroutine builder, such as launch or async.
import kotlinx.coroutines.*
fun main() = runBlocking {
println("Starts function")
launch {
println("Coroutine started")
delay(3000) // Pause the coroutine for 3 seconds
println("Coroutine resumed after delay")
}
println("End function")
}
In this example, the launch coroutine starts a new coroutine that prints messages before and after the delay(3000) statement. The delay() function causes the coroutine to pause for 3 seconds, allowing other tasks or coroutines to run in the meantime. The output will demonstrate the non-blocking nature of the delay() function.
Happy learning !