Kotlin 协程实战:实现异步值加载委托,对值进行异步懒初始化

在实际开发中,我们经常遇到这样的场景。

  • 需要立即初始化但计算成本高昂的值
kotlin 复制代码
val expensiveValue = calculateExpensiveValue() 
  • 可能引发阻塞的初始化操作
kotlin 复制代码
val resource = loadResourceFromNetwork()

这些场景通常需要满足以下需求:

  1. 异步加载:避免阻塞主线流程
  2. 惰性计算:按需获取结果
  3. 结果缓存:多次访问时直接返回
  4. 状态同步:支持阻塞等待和异步等待
  5. 动态更新:允许主动设置最终值

异步计算值

通过 Kotlin 协程的 async/await 模型实现异步计算能力。AsyncValue 在初始化时立即通过 CoroutineScope.async 启动后台计算任务,并通过 Deferred.await() 实现挂起等待:

kotlin 复制代码
var finished = false
var result: T? = null
var exception: Throwable? = null
private var production: Deferred<T?> = CoroutineScope(dispatcher).async {
    runCatching { producer() }
        .onFailure { exception = it }
        .onSuccess { result = result ?: it }
        .getOrNull()
}

关键实现细节:

  1. 协程调度 :使用 Dispatchers.IO 作为默认调度器,适用于 IO 密集型任务
  2. 原子操作 :通过 runCatching 统一处理异常,确保结果状态一致性
  3. 结果缓存 :计算完成后将结果存储在 result 字段中,后续访问直接返回
  4. 异常传递 :异常信息会保留在 exception 字段中,等待消费时抛出

委托方式返回和设置值

通过实现 getValue/setValue 操作符函数,提供属性委托访问能力:

kotlin 复制代码
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
    val result = runBlocking { await() }
    if (result == null && exception != null) throw exception!!
    if (result == null) throw NullPointerException("The value[$property] produced is null")
    return result
}

/**
* 获取值,如果值还未生产完成,则挂起协程等待
*/
suspend fun await(): T? = result ?: production.await().apply {
   if (exception != null) throw exception!!
}

特性说明:

  1. 阻塞等待 :在普通上下文访问时,通过 runBlocking 阻塞当前线程等待结果
  2. 异常传播:保留原始异常堆栈信息,便于调试定位
  3. 空值防护:禁止返回 null 值,强制显式处理空值情况

动态更新能力:

kotlin 复制代码
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
    runCatching { production.cancel() }
    result = value
}

更新流程:

  1. 取消正在进行的异步计算
  2. 直接设置最终值
  3. 后续访问将返回新值

使用示例:

kotlin 复制代码
class ImageLoader {
    var currentImage: BufferedImage by AsyncValue {
        ImageIO.read(File("big_image.png")) // 耗时加载
    }

    // 动态切换图片
    fun changeImage(newImage: BufferedImage) {
        currentImage = newImage
    }
	
	// 允许产生空值
	val optional: Optional<String> by AsyncValue {
	    Optional.ofNullable(null)
	}
}

完整代码

kotlin 复制代码
class AsyncValue<T>(
    dispatcher: CoroutineDispatcher = Dispatchers.IO,
    private val producer: suspend () -> T,
) {
    /**
     * 非委托下可以访问
     * 是否已经生产完成
     */
    var finished = false
        private set

    /**
     * 非委托下可以访问
     * 产生的值,如果值还未生产完成、异常报错、产生Null,则返回null
     */
    var result: T? = null
        private set

    /**
     * 非委托下可以访问
     * 产生的异常,如果异常未产生、未异常报错则返回null
     */
    var exception: Throwable? = null
        private set

    private var production: Deferred<T?> = CoroutineScope(dispatcher).async {
        runCatching { producer() }
            .onFailure { exception = it }
            .onSuccess { result = result ?: it }
            .getOrNull()
            .apply { finished = true }
    }

    /**
     * 获取值,如果值还未生产完成,则挂起协程等待
     */
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        val result = runBlocking { await() }
        if (result == null && exception != null) throw exception!!
        if (result == null) throw NullPointerException("The value[$property] produced is null")
        return result
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        runCatching { production.cancel() }
        result = value
    }

    /**
     * 获取值,如果值还未生产完成,则挂起协程等待
     */
    suspend fun await(): T? = result ?: production.await().apply {
        if (exception != null) throw exception!!
    }
}

典型应用场景

资源预加载

kotlin 复制代码
val font by AsyncValue {
    // 耗时字体加载
    Font.createFont(Font.TRUETYPE_FONT, File("big_font.ttf"))
}

网络请求聚合

kotlin 复制代码
class WeatherService {
    var weatherData by AsyncValue {
        apiService.fetchWeather() // 网络请求
    }

    fun showWeather() {
        // 页面显示时自动触发异步加载
        val data = weatherData 
        updateUI(data)
    }
}

计算缓存优化

kotlin 复制代码
object DataProcessor {
    private val processedData by AsyncValue {
        heavyComputation().also { 
            saveToCache(it) // 自动缓存计算结果
        }
    }

    fun getData() = processedData
}
相关推荐
晨枫阳2 分钟前
Vue 前端构建工具链完全理解指南
前端·javascript·vue.js
言乐62 分钟前
Python关于老游复刻与创新2
前端·javascript·css·python·css3
ly76893 分钟前
前端测试完整指南:从单元测试、组件测试到端到端测试与 CI/CD
前端·ci/cd·单元测试
迷枫7121 小时前
SQL性能排查记录
java·数据库·sql
渡我白衣1 小时前
深入理解 Transformer:Transformer 究竟是什么?
java·linux·开发语言·c++·人工智能·深度学习·transformer
Tisfy4 小时前
LeetCode 1927.求和游戏:抵消+看最值
java·leetcode·游戏·题解·博弈论
钛态9 小时前
Vite 中的 CSS 工程化:从 CSS Modules 到 UnoCSS 的渐进式迁移
前端·vue·react·web
秋饼10 小时前
JDK 27 企业级 AI 服务落地实战:G1 默认、紧凑对象头、后量子 TLS 与 JFR 脱敏全解析
java·ai·技术分享·后端开发
Csvn10 小时前
原型链、this 与闭包内存:三座深水区一次打通(含内存泄漏排查实战)
前端·javascript
卓怡学长11 小时前
w176基于SpringBoot的医院管理系统
java·spring boot·spring·maven·intellij-idea