Coroutines
## Learning Objectives
- Understand coroutines and their benefits
- Use suspend functions
- Launch coroutines with launch and async
- Work with coroutine builders
- Manage coroutine scopes and dispatchers
## What are Coroutines?
Coroutines are Kotlin's solution for asynchronous programming. They are lightweight threads that can be suspended and resumed.
### vs Threads
- Threads are heavy, coroutines are lightweight
- Millions of coroutines can run on few threads
- Coroutines can be suspended without blocking
```kotlin
import kotlinx.coroutines.*
fun main() {
println("Starting...")
// Launch a coroutine
GlobalScope.launch {
delay(1000) // Non-blocking delay
println("Inside coroutine")
}
println("After launch...")
Thread.sleep(2000) // Keep main thread alive
println("Done")
}
```
## Suspend Functions
`suspend` marks a function that can be paused and resumed:
```kotlin
import kotlinx.coroutines.*
suspend fun fetchData(): String {
delay(1000) // Suspends without blocking
return "Data loaded"
}
fun main() = runBlocking {
println("Before")
val data = fetchData()
println(data)
println("After")
}
```
## Coroutine Builders
### launch
Fire and forget - doesn't return result:
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
delay(1000)
println("Task completed")
}
println("Waiting...")
job.join() // Wait for job to complete
println("Done")
}
```
### async
Returns a result via Deferred:
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
val deferred = async {
delay(1000)
"Result"
}
println("Waiting for result...")
val result = deferred.await()
println("Result: $result")
}
```
### runBlocking
Blocks current thread until coroutines complete:
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(500)
println("Task 1")
}
launch {
delay(300)
println("Task 2")
}
println("Waiting...")
}
```
## Structured Concurrency
### coroutineScope
Creates a scope that waits for all children:
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
coroutineScope {
launch {
delay(1000)
println("Task 1")
}
launch {
delay(500)
println("Task 2")
}
}
println("All tasks completed")
}
```
### launch in coroutineScope
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
doWork()
}
suspend fun doWork() = coroutineScope {
launch {
delay(200)
println("Work 1")
}
launch {
delay(100)
println("Work 2")
}
println("Scope started")
}
```
## Dispatchers
Control which thread runs the coroutine:
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
launch { // Default dispatcher
println("Default: ${Thread.currentThread().name}")
}
launch(Dispatchers.IO) {
println("IO: ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) {
println("Default: ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) {
println("Unconfined: ${Thread.currentThread().name}")
}
}
```
### With Context
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
launch(Dispatchers.Default) {
println("Running in Default dispatcher")
}
withContext(Dispatchers.IO) {
println("Running in IO dispatcher")
}
}
```
## Exception Handling
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
try {
repeat(1000) { i ->
println("Working $i")
delay(500)
}
} finally {
println("Cleanup!")
}
}
delay(2000)
job.cancelAndJoin()
println("Cancelled")
}
```
### withTimeout
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
try {
withTimeout(1500) {
repeat(1000) { i ->
println("Working $i")
delay(500)
}
}
} catch (e: TimeoutCancellationException) {
println("Timed out!")
}
}
```
## Sequential Execution
Without coroutines:
```kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.experimental.*
suspend fun doTask1(): String {
delay(1000)
return "Task 1"
}
suspend fun doTask2(): String {
delay(1000)
return "Task 2"
}
fun main() = runBlocking {
val time = measureTimeMillis {
val result1 = doTask1()
val result2 = doTask2()
println("$result1, $result2")
}
println("Took ${time}ms") // ~2000ms
}
```
With async:
```kotlin
fun main() = runBlocking {
val time = measureTimeMillis {
val result1 = async { doTask1() }
val result2 = async { doTask2() }
println("${result1.await()}, ${result2.await()}")
}
println("Took ${time}ms") // ~1000ms
}
```
## Cancellation
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
repeat(1000) { i ->
println("Working $i")
delay(500)
}
}
delay(2000)
println("Cancelling...")
job.cancel()
job.join()
println("Cancelled")
}
```
### isActive Check
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
var i = 0
while (isActive) { // Check if still active
println("Working $i")
delay(500)
i++
}
}
delay(2000)
job.cancelAndJoin()
println("Done")
}
```
## Flows
Flows are cold asynchronous streams:
```kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
// Create a flow
val flow = flow {
for (i in 1..5) {
delay(500)
emit(i)
}
}
// Collect flow
flow.collect { value ->
println("Received: $value")
}
}
```
### Flow Operators
```kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
(1..5).asFlow()
.map { it * it }
.filter { it > 5 }
.collect { println(it) }
}
```
## Summary
- Coroutines are lightweight async building blocks
- `suspend` marks functions that can be paused
- `launch` for fire-and-forget, `async` for results
- `runBlocking` blocks current thread
- `Dispatchers.IO`, `Dispatchers.Default`, `Dispatchers.Main`
- Structured concurrency with `coroutineScope`
- `cancel()` and `withTimeout` for cancellation
- Flows are cold asynchronous streams
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →