Kotlin Coroutines By Example (Exception Handling, Delay, Timeout & More)
Introduction
Coroutines are a simple way to implement asynchronous and non-blocking code. It's implemented using suspending functions at the language level and with the help of the kotlinx.coroutines library.
Coroutine scopes and builders are used to define Coroutines.
To better understand Kotlin Coroutine concepts such as suspending functions, builders, scopes and contexts, you can follow this previously posted article.
Execute code in the background and continue
import kotlinx.coroutines.*
fun main() {
println("The main program is started")
GlobalScope.launch {
println("Background processing started")
delay(500L)
println("Background processing finished")
}
println("The main program continues")
runBlocking {
delay(1000L)
println("The main program is finished")
}
}
Concurrent code execution
Launch multiple computations in parallel and wait for them to finish
import kotlinx.coroutines.*
import java.text.SimpleDateFormat
import java.util.*
fun main() = runBlocking {
val deferred1 = async { computation1() }
val deferred2 = async { computation2() }
printCurrentTime("Awaiting computations...")
val result = deferred1.await() + deferred2.await()
printCurrentTime("The result is $result")
}
suspend fun computation1(): Int {
delay(1000L) // simulated computation
printCurrentTime("Computation1 finished")
return 131
}
suspend fun computation2(): Int {
delay(2000L)
printCurrentTime("Computation2 finished")
return 9
}
fun printCurrentTime(message: String) {
val time = (SimpleDateFormat("hh:mm:ss")).format(Date())
println("[$time] $message")
}
Cancelling a coroutine execution
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
// Emulate some batch processing
repeat(30) { i ->
println("Processing $i ...")
delay(300L)
}
}
delay(1000L)
println("main: The user requests the cancellation of the processing")
job.cancelAndJoin() // cancel the job and wait for it's completion
println("main: The batch processing is cancelled")
}
Execution timeout
Run a given block of code inside a coroutine with a specified timeout.
Throw exception on timeout
A TimeoutCancellationException is thrown if the timeout is exceeded.
import kotlinx.coroutines.*
fun main() = runBlocking {
withTimeout(1000L) {
repeat(30) { i ->
println("Processing $i ...")
delay(300L)
}
}
}
Return null on timeout
import kotlinx.coroutines.*
fun main() = runBlocking {
val status = withTimeoutOrNull(1000L) {
repeat(30) { i ->
println("Processing $i ...")
delay(300L)
}
"Finished"
}
println("The processing return status is: $status")
}
Exception handling
When using launch
By default, the exceptions thrown inside a coroutine started with launch
don't require to be handled and are printed instead to the console. They are treated as uncaught exceptions.
However, they still can be handled by using a CoroutineExceptionHandler.
import kotlinx.coroutines.*
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("$exception handled !")
}
val job = GlobalScope.launch(handler) {
throw UnsupportedOperationException()
}
job.join()
}
When using async
import kotlinx.coroutines.*
fun main() = runBlocking {
val deferred = GlobalScope.async {
// some business logic
throw UnsupportedOperationException()
}
try {
deferred.await() // The exception is thrown here
println("Won't be printed")
} catch (e: UnsupportedOperationException) {
println("UnsupportedOperationException handled !")
}
}
Android usage
Setup
To be able to launch coroutines with the Dispatchers.Main
context, you have to add the kotlinx-coroutines-android
dependency to your project. For example, when using Gradle, add the following line to your app/build.gradle
file inside your dependencies
:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1"
Examples
Coroutines are commonly used on View Models to fetch data from a database or from the Internet.
import androidx.lifecycle.*
import kotlinx.coroutines.*
// Android View Model (holds data to be displayed in a view)
class MyViewModel : ViewModel() {
private val _properties = MutableLiveData<List<MyData>>()
val properties: LiveData<List<MyData>>
get() = _properties
// To be able to cancel launched coroutines (if any)
private val job = Job()
// The coroutine runs using the Main (UI) dispatcher
private val coroutineScope = CoroutineScope(job + Dispatchers.Main)
init {
coroutineScope.launch {
val deferred: Deferred<List<MyData>> = MyService.getPropsAsync()
_properties.value = deferred.await()
}
}
// Cancel the job when the view model is destroyed
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
Soufiane Sakhi is an AWS Certified Solutions Architect – Associate and a professional full stack developer.