Blog Infos
Author
Published
Topics
, , , ,
Author
Published
🤔 Problem Statement

In October 2018, a GitHub user proposed introducing a suspending version of Kotlin’s lazy { ... } function to handle expensive initializations without blocking threads. While lazy effectively defers initialization until needed, it can still block execution, making it less suitable for coroutine-based, non-blocking applications. To solve this, contributors suggested using async(start = LAZY), allowing initialization to be deferred and executed asynchronously on first access. Several custom coroutine-based implementations emerged to bridge this gap, but despite strong interest, the feature was never integrated into the standard Kotlin library.

As of now, the Kotlin standard library does not include a built-in suspending version of the lazy function. The discussion on GitHub Issue #706 concluded without integrating this feature into the library. In the meantime, developers have explored alternative approaches, such as Mr Roman Elizarov, from his gist

📣 📣 📣 Everyone finding this gist via Google! Modern kotlinx.coroutines has out-of-the-box support for asyncLazy with the following expression: val myLazyValue = async(start = CoroutineStart.LAZY) { ... }. Use myLazyValue.await() when you need it.

as well as lazily-started-async. However, it’s important to note that the use of CoroutineStart.LAZY has been debated within the community. A recent discussion in GitHub Issue #4147 considered discouraging its use due to potential complexities and difficulties in code readability.

Given these considerations, if you require suspending lazy initialization, you might opt for a custom implementation tailored to your specific use case. So, how can we implement true non-blocking lazy initialization in coroutines?

Let’s explore some practical solutions. 🚀

  • 🎯 Introduction
  • 🔥 Implementation
  • 🏆 Conclusion
  • 🌐 References
🎯 Introduction

Lazy initialization is a powerful pattern that delays object creation until it’s actually needed, improving performance and resource management. But what if you need to initialize a value asynchronously inside Kotlin coroutines? That’s where LazySuspend comes in! 🌟

🛠 Why Do We Need LazySuspend?

Kotlin provides lazy {} for synchronous lazy initialization, but it does not support suspending functions. Imagine you need to load data from a database or fetch an API response asynchronously. 🤯 Consider this example:

val storageProvider by lazy {
    initializeStorageProvider() // Cannot be a suspend function 😢
}

suspend fun initializeStorageProvider(){
   // ... long-running task
}

This won’t work if initializeStorageProvider is a suspend function! Instead, we need a coroutine-friendly lazy initialization mechanism. 💡

🔥 Implementation
1️⃣ Approach 1

 

import kotlinx.coroutines.*
import kotlin.coroutines.*

class LazySuspend<T>(private val initializer: suspend () -> T) {
    @Volatile
    private var cachedValue: T? = null
    private val mutex = Mutex()

    suspend fun getValue(): T {
        if (cachedValue != null) return cachedValue!!

        return mutex.withLock {
            if (cachedValue == null) {
                cachedValue = initializer()
            }
            cachedValue!!
        }
    }
}

 

  • ✅ Uses a suspending function for initialization.
  • ✅ Uses a mutex (withLock) to ensure thread safety (prevents race conditions in multithreading).
  • ✅ Stores the computed value after the first call, so subsequent calls return instantly.

 

suspend fun main() {
    val lazyValue = LazySuspend {
        println("Initializing...")
        delay(1000)  // Simulate long computation
        "Hello, Coroutine Lazy!"
    }

    println("Before accessing value...")
    println("Value: ${lazyValue.getValue()}")  // Triggers initialization
    println("Value again: ${lazyValue.getValue()}")  // Uses cached value
}

// output
Before accessing value...
Initializing...
Value: Hello, Coroutine Lazy!
Value again: Hello, Coroutine Lazy!

 

2️⃣ Approach 2: Deferred

 

class LazySuspendDeferred<T>(scope: CoroutineScope, initializer: suspend () -> T) {
    private val deferred = scope.async(start = CoroutineStart.LAZY) { initializer() }
    suspend fun getValue(): T = deferred.await()
}

 

3️⃣ Approach 3: SuspendLazy from kt.academy

This function allows deferred execution of a block of code that is initialized only once in a coroutine, similar to lazy initialization. It ensures thread safety by using a Mutex and provides mechanisms to handle initialization failures and context propagation, for further details, visit the original article.

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.currentTime
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlin.coroutines.CoroutineContext
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
/**
* https://kt.academy/article/s_suspended_lazy
*/
fun <T> suspendLazy(initializer: suspend () -> T): SuspendLazy<T>{
var innerInitializer: (suspend () -> T)? = initializer
val mutex = Mutex()
var holder: Any? = Any()
return object : SuspendLazy<T> {
override val isInitialized: Boolean
get() = innerInitializer == null
override fun valueOrNull(): T? =
if (isInitialized) holder as T else null
@Suppress("UNCHECKED_CAST")
override suspend fun invoke(): T =
if (isInitialized) holder as T
else mutex.withLock {
innerInitializer?.let {
holder = it()
innerInitializer = null
}
holder as T
}
}
}
interface SuspendLazy<T> : suspend () -> T {
val isInitialized: Boolean
fun valueOrNull(): T?
override suspend operator fun invoke(): T
}
view raw suspendLazy.kt hosted with ❤ by GitHub

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.currentTime
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlin.coroutines.CoroutineContext
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
/**
* https://kt.academy/article/s_suspended_lazy
*/
fun <T> suspendLazy(initializer: suspend () -> T): SuspendLazy<T>{
var innerInitializer: (suspend () -> T)? = initializer
val mutex = Mutex()
var holder: Any? = Any()
return object : SuspendLazy<T> {
override val isInitialized: Boolean
get() = innerInitializer == null
override fun valueOrNull(): T? =
if (isInitialized) holder as T else null
@Suppress("UNCHECKED_CAST")
override suspend fun invoke(): T =
if (isInitialized) holder as T
else mutex.withLock {
innerInitializer?.let {
holder = it()
innerInitializer = null
}
holder as T
}
}
}
interface SuspendLazy<T> : suspend () -> T {
val isInitialized: Boolean
fun valueOrNull(): T?
override suspend operator fun invoke(): T
}
view raw suspendLazy.kt hosted with ❤ by GitHub

 

4️⃣ Approach 4: LazySuspend from ME ✌️😊

Why I choose LazySuspend instead of SuspendLazy?

Both LazySuspend and SuspendLazy are reasonable names, but the better choice depends on readability, consistency, and convention.

  • Matches the existing lazy { ... } function in Kotlin.
  • Emphasizes “lazy” behavior first, making it clear this is an alternative to lazy { ... }.
  • Easier to recognize for Kotlin developers already familiar with lazy.

The approach 3 may have some ✨ potential improvements, so that’s why I come up with LazySuspend

✅ Avoid Unsafe Casts: The code currently casts holder to T, which might cause issues if holder was never properly assigned. Instead, you can use a sealed class or an AtomicReference.

🔐 Ensuring Thread-Safety with Mutex we ensure that only one coroutine initializes the value at a time, preventing race conditions. 🏎💨

⚠️ Handling Exceptions Gracefully: If the initializer fails, holder remains Any?, causing an unsafe cast, leading to a ClassCastException.

package com.nphausg.loom.coroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.concurrent.atomic.AtomicReference
/**
* Sealed class representing the state of a lazily initialized value.
* It can either be uninitialized, initialized with a value, or failed due to an exception.
*
* @param T The type of the value being lazily initialized.
*/
sealed class LazyState<out T> {
/**
* Represents an uninitialized state.
*/
data object Uninitialized : LazyState<Nothing>()
/**
* Represents an initialized state holding a value of type [T].
*
* @param value The initialized value of type [T].
*/
data class Initialized<T>(val value: T) : LazyState<T>()
/**
* Represents a failed state containing an exception.
*
* @param exception The exception that occurred during initialization.
*/
data class Failed(val exception: Throwable) : LazyState<Nothing>()
}
/**
* A functional interface representing a suspending function that can return a value of type [T].
* This interface also provides properties and functions to check the initialization state and
* retrieve the value if it's available.
*/
interface LazySuspend<T> : suspend () -> T {
/**
* A property indicating if the value has been initialized.
*/
val isInitialized: Boolean
/**
* Returns the value if it's initialized, or `null` if it's uninitialized or failed.
*
* @return The initialized value of type [T] or `null`.
*/
fun getOrNull(): T?
/**
* Suspends the execution and retrieves the lazily initialized value.
* If not initialized, it will invoke the initializer function.
*
* @return The lazily initialized value of type [T].
*/
override suspend operator fun invoke(): T
}
/**
* Creates a [LazySuspend] instance, which lazily initializes a value using the provided [initializer].
* The initialization is done in a thread-safe manner, using double-checked locking.
*
* If the initialization fails, the state will be marked as failed and the exception will be thrown.
*
* @param T The type of the lazily initialized value.
* @param initializer A suspending function that provides the value to be lazily initialized.
* @return A [LazySuspend] instance that encapsulates the lazy initialization logic.
* @author <a href="https://github.com/nphausg">nphausg</>
*/
fun <T> lazySuspend(initializer: suspend () -> T): LazySuspend<T> {
val state = AtomicReference<LazyState<T>>(LazyState.Uninitialized)
val mutex = Mutex()
return object : LazySuspend<T> {
override val isInitialized: Boolean
get() = state.get() is LazyState.Initialized
override fun getOrNull(): T? =
(state.get() as? LazyState.Initialized)?.value
override suspend fun invoke(): T {
val currentState = state.get()
if (currentState is LazyState.Initialized) {
return currentState.value
}
return mutex.withLock {
val doubleCheck = state.get()
if (doubleCheck is LazyState.Initialized) {
return doubleCheck.value
}
try {
val result = initializer()
state.set(LazyState.Initialized(result))
result
} catch (e: Throwable) {
state.set(LazyState.Failed(e))
throw e
}
}
}
}
}
view raw lazySuspend.kt hosted with ❤ by GitHub

https://gist.github.com/nphausg/d370986b1575b7c75085a6132bc123ae

 

  • LazyState Sealed Class: This class is used to represent the current state of a value, whether it’s uninitialized, initialized with a value, or failed due to an exception.
  • LazySuspend Interface: This interface extends a suspending function (suspend () -> T) and adds additional properties and methods:
  • isInitialized: A boolean property to check if the value has been initialized.
  • getOrNull(): Returns the value if initialized, or null if not.
  • invoke(): The main suspending function to retrieve the lazily initialized value.
  • lazySuspend Function: This function creates an instance of LazySuspend that lazily initializes a value using the provided suspending function (initializer). It uses atomic references for thread-safety and double-checked locking to ensure that the value is initialized only once.

How can I ensure that the LazySuspend initialization runs on a background thread instead of the main thread in Kotlin?

To ensure that the lazySuspend initialization does not run on the main thread, you can explicitly use a different coroutine dispatcher when invoking the suspending function inside the initializer. You can use Dispatchers.IODispatchers.Default, or any custom dispatcher to offload the work to a background thread. Here’s how you can modify your lazySuspend initialization to run on a background thread:

import kotlinx.coroutines.*

val lazyValue = lazySuspend {
    withContext(Dispatchers.IO) {  // Ensure this runs on a background thread
        println("Initialized on thread: ${Thread.currentThread().name}")
        longRunningTask()  // Simulate some background work
    }
}

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Kobweb:Creating websites in Kotlin leveraging Compose HTML

Kobweb is a Kotlin web framework that aims to make web development enjoyable by building on top of Compose HTML and drawing inspiration from Jetpack Compose.
Watch Video

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kobweb

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author o ...

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kob ...

Jobs

No results found.

🚨 Disclaimers

Approach 4 may have some disadvantages:

  • Complexity: The custom implementation adds complexity compared to Kotlin’s built-in lazy.
  • Potential Overhead: Double-checked locking may introduce unnecessary overhead in single-threaded scenarios.
  • Limited Use Case: The extra control may not be required for simpler lazy initialization needs.
🏆 Conclusion

The LazySuspend interface includes methods to check if the value is initialized (isInitialized), retrieve it if available (getOrNull()), and lazily initialize it when accessed (invoke()). The LazySuspend provides lazy, suspend-aware initialization while ensuring thread safety and error handling. 🚀 Whether you’re fetching API data, caching results, or managing expensive computations, LazySuspend is a powerful tool in your Kotlin arsenal.

Give it a try in your next project! 🛠️

// Step 1: Grab from Maven central at the coordinates:

repositories {
  google()
  mavenCentral()
  maven {
    url = uri("https://maven.pkg.github.com/nphausg/loomIn")
  }
}

// Step 2: Implementation from your module
$latestVersion = "0.0.1-alpha"
implementation("com.nphausg:loom:$latestVersion")
🌐 References
  • Kotlin Coroutines Documentation — Kotlinlang.org
  • Mutex in Kotlin Coroutines — Kotlin Coroutines Guide
  • Lazy Initialization in Kotlin — JetBrains Blog
  • AtomicReference in Java — Java Documentation

This article is previously published on proandroiddev.com.

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
READ MORE
Menu