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
}
相关推荐
予枫的编程笔记8 分钟前
【Java集合】深入浅出 Java HashMap:从链表到红黑树的“进化”之路
java·开发语言·数据结构·人工智能·链表·哈希算法
ohoy14 分钟前
RedisTemplate 使用之Set
java·开发语言·redis
mjhcsp14 分钟前
C++ 后缀数组(SA):原理、实现与应用全解析
java·开发语言·c++·后缀数组sa
Hilaku24 分钟前
我用 Gemini 3 Pro 手搓了一个并发邮件群发神器(附源码)
前端·javascript·github
IT_陈寒24 分钟前
Java性能调优实战:5个被低估却提升30%效率的JVM参数
前端·人工智能·后端
快手技术25 分钟前
AAAI 2026|全面发力!快手斩获 3 篇 Oral,12 篇论文入选!
前端·后端·算法
颜酱27 分钟前
前端算法必备:滑动窗口从入门到很熟练(最长/最短/计数三大类型)
前端·后端·算法
8***f39528 分钟前
Spring容器初始化扩展点:ApplicationContextInitializer
java·后端·spring
r_oo_ki_e_34 分钟前
java22--常用类
java·开发语言
全栈前端老曹35 分钟前
【包管理】npm init 项目名后底层发生了什么的完整逻辑
前端·javascript·npm·node.js·json·包管理·底层原理