在实际开发中,我们经常遇到这样的场景。
- 需要立即初始化但计算成本高昂的值
kotlin
val expensiveValue = calculateExpensiveValue()
- 可能引发阻塞的初始化操作
kotlin
val resource = loadResourceFromNetwork()
这些场景通常需要满足以下需求:
- 异步加载:避免阻塞主线流程
- 惰性计算:按需获取结果
- 结果缓存:多次访问时直接返回
- 状态同步:支持阻塞等待和异步等待
- 动态更新:允许主动设置最终值
异步计算值
通过 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()
}
关键实现细节:
- 协程调度 :使用
Dispatchers.IO
作为默认调度器,适用于 IO 密集型任务 - 原子操作 :通过
runCatching
统一处理异常,确保结果状态一致性 - 结果缓存 :计算完成后将结果存储在
result
字段中,后续访问直接返回 - 异常传递 :异常信息会保留在
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!!
}
特性说明:
- 阻塞等待 :在普通上下文访问时,通过
runBlocking
阻塞当前线程等待结果 - 异常传播:保留原始异常堆栈信息,便于调试定位
- 空值防护:禁止返回 null 值,强制显式处理空值情况
动态更新能力:
kotlin
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
runCatching { production.cancel() }
result = value
}
更新流程:
- 取消正在进行的异步计算
- 直接设置最终值
- 后续访问将返回新值
使用示例:
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
}