🎛️ Context & Dispatcher
📖 什么是 CoroutineContext?
CoroutineContext 是决定协程如何执行的配置集合。它指定在哪个线程上执行、名称是什么等!
💡 Dispatcher
基本 Dispatcher
fun main() = runBlocking {
// Main - UI 线程 (Android/Desktop)
launch(Dispatchers.Main) {
// UI 更新
}
// IO - 网络/文件操作
launch(Dispatchers.IO) {
println("IO: ${Thread.currentThread().name}")
}
// Default - CPU 密集型任务
launch(Dispatchers.Default) {
println("Default: ${Thread.currentThread().name}")
}
// Unconfined - 特殊情况
launch(Dispatchers.Unconfined) {
println("Unconfined: ${Thread.currentThread().name}")
}
delay(100)
}
Dispatchers.IO
suspend fun readFile(): String = withContext(Dispatchers.IO) {
// 文件读取、网络请求等
delay(1000)
"文件内容"
}
suspend fun writeFile(content: String) = withContext(Dispatchers.IO) {
// 文件写入
delay(500)
println("文件保存: $content")
}
fun main() = runBlocking {
val content = readFile()
writeFile(content)
}
Dispatchers.Default
suspend fun heavyComputation(): Int = withContext(Dispatchers.Default) {
// CPU 密集型计算
var result = 0
repeat(1_000_000) {
result += it
}
result
}
fun main() = runBlocking {
val result = heavyComputation()
println("计算结果: $result")
}
🎯 实战示例
按层级使用 Dispatcher
// Repository - IO
class UserRepository {
suspend fun fetchUser(id: String): User = withContext(Dispatchers.IO) {
delay(1000) // 网络请求
User(id, "张三")
}
}
// UseCase - Default
class ProcessUserUseCase {
suspend fun process(user: User): ProcessedUser = withContext(Dispatchers.Default) {
// 数据处理
delay(500)
ProcessedUser(user.name.uppercase())
}
}
data class User(val id: String, val name: String)
data class ProcessedUser(val displayName: String)
fun main() = runBlocking {
val repo = UserRepository()
val useCase = ProcessUserUseCase()
val user = repo.fetchUser("123")
val processed = useCase.process(user)
println("结果: ${processed.displayName}")
}
并行 IO 操作
suspend fun loadAllData(): Triple<String, String, String> = coroutineScope {
val user = async(Dispatchers.IO) {
delay(1000)
"用户数据"
}
val posts = async(Dispatchers.IO) {
delay(1500)
"帖子数据"
}
val comments = async(Dispatchers.IO) {
delay(800)
"评论数据"
}
Triple(user.await(), posts.await(), comments.await())
}
fun main() = runBlocking {
val time = measureTimeMillis {
val (user, posts, comments) = loadAllData()
println("$user, $posts, $comments")
}
println("耗时: ${time}ms") // ~1500ms (并行)
}
🔧 Context 组合
命名
fun main() = runBlocking {
launch(CoroutineName("任务1")) {
println("名称: ${coroutineContext[CoroutineName]}")
}
launch(Dispatchers.IO + CoroutineName("IO任务")) {
println("线程: ${Thread.currentThread().name}")
println("名称: ${coroutineContext[CoroutineName]}")
}
delay(100)
}
添加 Job
fun main() = runBlocking {
val job = Job()
launch(job + Dispatchers.Default) {
println("任务执行")
delay(1000)
println("任务完成")
}
delay(500)
println("任务取消")
job.cancel()
}
🎨 withContext
线程切换
suspend fun complexTask() {
println("开始: ${Thread.currentThread().name}")
// IO 操作
val data = withContext(Dispatchers.IO) {
println("IO: ${Thread.currentThread().name}")
"数据"
}
// CPU 操作
val processed = withContext(Dispatchers.Default) {
println("Default: ${Thread.currentThread().name}")
data.uppercase()
}
println("结束: ${Thread.currentThread().name}")
println("结果: $processed")
}
fun main() = runBlocking {
complexTask()
}
优化模式
// ❌ 不必要的切换
suspend fun bad() {
withContext(Dispatchers.IO) {
val data1 = loadData1()
withContext(Dispatchers.Default) { // 不必要!
process(data1)
}
}
}
// ✅ 高效
suspend fun good() {
val data1 = withContext(Dispatchers.IO) {
loadData1()
}
withContext(Dispatchers.Default) {
process(data1)
}
}
suspend fun loadData1() = delay(100)
suspend fun process(data: Unit) = delay(100)
🔥 实用模式
缓存 + 网络
class DataSource {
private var cache: String? = null
suspend fun getData(): String {
// 检查缓存 (快)
cache?.let { return it }
// 网络请求 (慢)
return withContext(Dispatchers.IO) {
delay(1000)
"新数据"
}.also { cache = it }
}
}
fun main() = runBlocking {
val source = DataSource()
// 第一次调用 - 网络
val time1 = measureTimeMillis {
println(source.getData())
}
println("第一次调用: ${time1}ms")
// 第二次 - 缓存
val time2 = measureTimeMillis {
println(source.getData())
}
println("第二次: ${time2}ms")
}
批处理
suspend fun processBatch(items: List<Int>): List<Int> {
return withContext(Dispatchers.Default) {
items.map { item ->
// 处理每个项目
item * 2
}
}
}
fun main() = runBlocking {
val items = List(100) { it }
val results = processBatch(items)
println("处理完成: ${results.size}个")
}
配合超时
suspend fun fetchWithTimeout(): String? {
return try {
withTimeout(2000) {
withContext(Dispatchers.IO) {
delay(3000) // 耗时太长
"数据"
}
}
} catch (e: TimeoutCancellationException) {
null
}
}
fun main() = runBlocking {
val result = fetchWithTimeout()
println("结果: ${result ?: "超时"}")
}
🛡️ 异常处理
CoroutineExceptionHandler
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("错误处理: ${exception.message}")
}
val job = launch(handler) {
throw Exception("发生问题!")
}
job.join()
println("继续执行")
}
SupervisorJob
fun main() = runBlocking {
val supervisor = SupervisorJob()
with(CoroutineScope(coroutineContext + supervisor)) {
val job1 = launch {
delay(500)
throw Exception("任务1失败")
}
val job2 = launch {
delay(1000)
println("任务2成功!")
}
try {
job1.join()
} catch (e: Exception) {
println("任务1异常: ${e.message}")
}
job2.join()
}
}
🎯 自定义 Dispatcher
指定线程池大小
fun main() = runBlocking {
val customDispatcher = Dispatchers.IO.limitedParallelism(2)
repeat(5) { i ->
launch(customDispatcher) {
println("任务 $i: ${Thread.currentThread().name}")
delay(1000)
}
}
delay(3000)
}
单线程
fun main() = runBlocking {
val singleThread = Dispatchers.Default.limitedParallelism(1)
repeat(3) { i ->
launch(singleThread) {
println("任务 $i: ${Thread.currentThread().name}")
delay(500)
}
}
delay(2000)
}
🤔 常见问题
Q1. 应该使用哪个 Dispatcher?
A: 根据任务类型选择!
// IO - 网络、文件、数据库
suspend fun fetchData() = withContext(Dispatchers.IO) { }
// Default - CPU 密集型计算
suspend fun compute() = withContext(Dispatchers.Default) { }
// Main - UI 更新 (Android/Desktop)
suspend fun updateUI() = withContext(Dispatchers.Main) { }
Q2. 可以多次使用 withContext 吗?
A: 可以!需要时随时切换。
suspend fun workflow() {
val data = withContext(Dispatchers.IO) {
loadFromNetwork()
}
val processed = withContext(Dispatchers.Default) {
processData(data)
}
withContext(Dispatchers.Main) {
updateUI(processed)
}
}
Q3. 不指定 Dispatcher 会怎样?
A: 继承父协程的 Context!
fun main() = runBlocking(Dispatchers.Default) {
launch { // 继承 Dispatchers.Default
println(Thread.currentThread().name)
}
}
🎬 总结
使用 Context 和 Dispatcher 控制协程!
核心要点:
✅ Dispatchers.IO - 网络/文件
✅ Dispatchers.Default - CPU 任务
✅ Dispatchers.Main - UI 更新
✅ withContext 切换线程
✅ Context 组合实现精细控制
恭喜!您已完成 Coroutines 系列!🎉
下一步: 在单元测试中学习如何编写测试!