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
}
相关推荐
我命由我1234511 分钟前
CSS - CSS 媒体查询 orientation
前端·javascript·css·html·css3·html5·js
lichenyang4531 小时前
让 VK 小程序调用 HarmonyOS 原生能力:一次跨端 Bridge SDK 的设计与实践
前端
阳光宅男@李光熠1 小时前
【电子通识】一起学习TDK的EMC基础——电池兼容设计方法概述
java·前端·数据库
张宜强1 小时前
025 安居租赁 · 房屋租赁平台
java·课程设计
Java_2017_csdn1 小时前
ReentrantLock 与 Redis RLock 对比解读
java
YDS8291 小时前
Small Spring IOC篇:实现 Bean 的定义、注册、获取
java·spring
边境悍匪1 小时前
蜗牛学苑 Java 智能体学习 Day45|Knife4j+SpringBoot3、书城项目整合 MyBatis 思维导图复盘
java·开发语言·vue.js·学习·spring
南雨北斗2 小时前
vue3项目中的env.d.ts文件
前端
烈风逍遥2 小时前
第五篇:通用 LLM 流式对话:前后端联接的完整实现
前端·后端·架构
aixingpan2 小时前
aixingpan.cn API开发文档:api_docs_trichart_natal_progression_sec_transit2接口指南
前端·php