π― Coroutine Basics
π Getting Started with Coroutinesβ
To use coroutines, you first need to understand coroutine builders. Let's learn about the three most basic builders!
π‘ runBlockingβ
Basic Usageβ
import kotlinx.coroutines.*
fun main() = runBlocking { // Start coroutine!
println("Hello")
delay(1000) // Wait 1 second
println("World!")
}
Characteristicsβ
fun main() {
println("Before")
runBlocking {
delay(1000)
println("Inside coroutine")
}
println("After") // Wait until coroutine finishes
}
// Before
// (wait 1 second)
// Inside coroutine
// After
Note: runBlocking blocks the current thread! Use it mainly for testing or in the main function only.
π launchβ
Starting Asynchronous Tasksβ
fun main() = runBlocking {
launch {
delay(1000)
println("Task 1")
}
launch {
delay(500)
println("Task 2")
}
println("Main")
}
// Main
// (after 500ms) Task 2
// (after 1000ms) Task 1
Job Returnβ
fun main() = runBlocking {
val job = launch {
repeat(5) { i ->
println("Working... $i")
delay(500)
}
}
delay(1300)
println("Canceling task!")
job.cancel() // Cancel task
job.join() // Wait for cancellation to complete
}
Managing Multiple Tasksβ
fun main() = runBlocking {
val jobs = List(5) { i ->
launch {
delay(1000L * i)
println("Task $i completed")
}
}
jobs.forEach { it.join() } // Wait for all tasks
println("All tasks finished!")
}
β‘ asyncβ
Receiving Resultsβ
fun main() = runBlocking {
val deferred = async {
delay(1000)
return@async "Result value"
}
println("Working...")
val result = deferred.await() // Wait for result
println("Received value: $result")
}
launch vs asyncβ
fun main() = runBlocking {
// launch - no result
launch {
delay(1000)
println("launch completed")
}
// async - returns result
val result = async {
delay(1000)
"async completed"
}
println(result.await())
}
π― Practical Examplesβ
Parallel Processingβ
import kotlin.system.measureTimeMillis
fun main() = runBlocking {
val time = measureTimeMillis {
val one = async { fetchData1() }
val two = async { fetchData2() }
println("Results: ${one.await()}, ${two.await()}")
}
println("Time elapsed: ${time}ms") // ~1000ms (parallel execution)
}
suspend fun fetchData1(): String {
delay(1000)
return "Data1"
}
suspend fun fetchData2(): String {
delay(1000)
return "Data2"
}
Sequential vs Parallelβ
fun main() = runBlocking {
// β Sequential execution (slow)
val time1 = measureTimeMillis {
val one = fetchData1()
val two = fetchData2()
println("$one, $two")
}
println("Sequential: ${time1}ms") // ~2000ms
// β
Parallel execution (fast)
val time2 = measureTimeMillis {
val one = async { fetchData1() }
val two = async { fetchData2() }
println("${one.await()}, ${two.await()}")
}
println("Parallel: ${time2}ms") // ~1000ms
}
Exception Handlingβ
fun main() = runBlocking {
val deferred = async {
delay(1000)
throw Exception("Error occurred!")
}
try {
deferred.await()
} catch (e: Exception) {
println("Caught error: ${e.message}")
}
}
π₯ Practical Patternsβ
Timeoutβ
fun main() = runBlocking {
try {
withTimeout(1300) {
repeat(3) { i ->
println("Task $i")
delay(500)
}
}
} catch (e: TimeoutCancellationException) {
println("Timed out!")
}
}
Multiple API Callsβ
data class UserData(
val profile: String,
val posts: List<String>,
val friends: List<String>
)
suspend fun loadUserData(userId: String): UserData = coroutineScope {
val profile = async { fetchProfile(userId) }
val posts = async { fetchPosts(userId) }
val friends = async { fetchFriends(userId) }
UserData(
profile.await(),
posts.await(),
friends.await()
)
}
suspend fun fetchProfile(id: String): String {
delay(500)
return "Profile($id)"
}
suspend fun fetchPosts(id: String): List<String> {
delay(800)
return listOf("Post1", "Post2")
}
suspend fun fetchFriends(id: String): List<String> {
delay(600)
return listOf("Friend1", "Friend2")
}
Retry Logicβ
suspend fun <T> retryIO(
times: Int = 3,
delay: Long = 1000,
block: suspend () -> T
): T {
repeat(times - 1) { attempt ->
try {
return block()
} catch (e: Exception) {
println("Attempt ${attempt + 1} failed")
delay(delay)
}
}
return block() // Last attempt
}
fun main() = runBlocking {
val result = retryIO(times = 3) {
fetchDataFromAPI()
}
println(result)
}
var attempt = 0
suspend fun fetchDataFromAPI(): String {
attempt++
if (attempt < 3) {
throw Exception("Network error")
}
return "Success!"
}
π οΈ coroutineScopeβ
Structured Concurrencyβ
suspend fun doWork() = coroutineScope {
launch {
delay(1000)
println("Task 1")
}
launch {
delay(2000)
println("Task 2")
}
println("All tasks started")
// Wait until all children finish
}
fun main() = runBlocking {
doWork()
println("Done!")
}
Exception Propagationβ
suspend fun riskyWork() = coroutineScope {
launch {
delay(500)
throw Exception("Error!")
}
launch {
delay(1000)
println("This code won't run")
}
}
fun main() = runBlocking {
try {
riskyWork()
} catch (e: Exception) {
println("Error caught: ${e.message}")
}
}
π€ Frequently Asked Questionsβ
Q1. What's the difference between launch and async?β
A: Whether it returns a result!
fun main() = runBlocking {
// launch - no result needed
launch {
println("Simple task")
}
// async - result needed
val result = async {
"Value to return"
}
println(result.await())
}
Q2. What's the difference between join() and await()?β
A: join waits for completion, await waits for result!
fun main() = runBlocking {
val job = launch {
delay(1000)
}
job.join() // Wait for completion only
val deferred = async {
delay(1000)
"Result"
}
val result = deferred.await() // Wait for result
}
Q3. Do coroutines cancel automatically?β
A: When a parent is cancelled, children are cancelled too!
fun main() = runBlocking {
val parent = launch {
val child = launch {
repeat(10) {
println("Child task $it")
delay(500)
}
}
delay(1300)
println("Parent completed")
}
delay(2000)
parent.cancel() // Children are cancelled together
}
π¬ Conclusionβ
You've mastered the basic coroutine builders!
Key Summary:
β
runBlocking - for testing/main function
β
launch - tasks that don't need results
β
async - for returning results
β
coroutineScope - structured concurrency
β
Performance improvement with parallel processing
Next Step: Learn more about suspending functions in suspend functions!