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 分钟前
RabbitMQ 进阶:延迟队列完全指南
java·分布式·spring·rabbitmq
小小小小宇3 分钟前
前端 WebRTC 全解析与应用
前端
phltxy4 分钟前
Spring AI Agents 智能体模式实战
java·人工智能·spring
华玥6 分钟前
优化滚动列表,使用虚拟滚动
前端
小小小小宇6 分钟前
前端 WebAssembly 全解析与应用
前端
摇滚侠14 分钟前
MyBatis 入门到项目实战 特殊 SQL 的执行 34-37
java·sql·mybatis
huangdong_14 分钟前
京东商品图片视频批量下载与m3u8视频合并技术完整实现方案
大数据·前端·数据库
尽兴-18 分钟前
4.1 智能体核心:Agent、Sub-Agent、ReAct、规划执行
前端·javascript·react.js·agent·react·subagent
小小小小宇31 分钟前
前端 Shadow DOM 全解析与应用
前端
万物更新_33 分钟前
vue框架
前端·javascript·vue.js·笔记