Hamba
11/23/2023, 1:08 PMclass myClass() {
var property: Any? = null
init {
// how to run this function asyncly without blocking?
property = funThatTakesAWhile()
}
private fun funThatTakesAWhile() {
// working...
return value
}
}
CLOVIS
11/23/2023, 1:15 PMclass MyClass(
scope: CoroutineScope,
) {
var property: Any? = null
private set
init {
scope.launch {
property = …
}
}
}
If you want to avoid the nullable
value, and you want the user to be able to know when the value finishes:
class MyClass(
scope: CoroutineScope,
) {
lateinit var val property: Deferred<Any>
private set
init {
property = scope.async {
…
}
}
}
Hamba
11/23/2023, 1:23 PMPatrick Steiger
11/23/2023, 1:24 PMCLOVIS
11/23/2023, 1:24 PMCLOVIS
11/23/2023, 1:25 PMHamba
11/23/2023, 1:26 PMCLOVIS
11/23/2023, 1:26 PMHamba
11/23/2023, 1:30 PMCLOVIS
11/23/2023, 1:31 PMCLOVIS
11/23/2023, 1:33 PMHamba
11/23/2023, 1:34 PMHamba
11/23/2023, 1:36 PMCLOVIS
11/23/2023, 1:36 PMMyClass
? When does it stop being useful?
• can multiple instances be created at the same time?Hamba
11/23/2023, 1:38 PMHamba
11/23/2023, 1:39 PMCLOVIS
11/23/2023, 1:41 PMCLOVIS
11/23/2023, 1:41 PMHamba
11/23/2023, 1:59 PMby mutablestateof()
so when it gets updated the composable recomposes and displays the new value. but trying to set the mutable property in a coroutine causes exception Exception in thread "DefaultDispatcher-worker-4" java.lang.IllegalStateException: Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied
if i dont use mutablestateof, and just set the property null initially and then set it in the coroutine, it works fine but doesnt trigger a recomposition. when i trigger a recomposition some other way, then it displays correctly.CLOVIS
11/23/2023, 2:07 PMclass MyClass(
foo: Int,
scope: CoroutineScope,
) {
var value by mutableStateOf<String?>(null)
init {
scope.launch {
delay(1000)
value = foo.toString()
}
}
}
@Composable
fun Display(value: Int) {
val scope = rememberCoroutineScope()
val myClass = remember(value) { MyClass(value, scope) }
Text(myClass.value ?: "Not loaded yet")
}
Hamba
11/23/2023, 2:12 PMHamba
11/23/2023, 2:14 PM@Composable
private fun TimelineSegment(image: ImageBitmap?) {
image?.let {
Image(
bitmap = it,
contentDescription = null
)
}
}
which at its core is thisCLOVIS
11/23/2023, 2:14 PMCLOVIS
11/23/2023, 2:14 PMHamba
11/23/2023, 2:14 PMHamba
11/23/2023, 2:14 PMHamba
11/23/2023, 2:15 PMHamba
11/23/2023, 2:16 PMCLOVIS
11/23/2023, 2:17 PMHamba
11/23/2023, 2:18 PMHamba
11/23/2023, 2:19 PMCLOVIS
11/23/2023, 2:19 PMHamba
11/23/2023, 2:19 PMHamba
11/23/2023, 2:20 PMCLOVIS
11/23/2023, 2:20 PMCLOVIS
11/23/2023, 2:21 PMHamba
11/23/2023, 2:21 PMHamba
11/23/2023, 2:22 PMHamba
11/23/2023, 2:22 PMCLOVIS
11/23/2023, 2:25 PMload
function and let compose do the work
class MyClass(…) {
var thumbnail by mutableStateOf<…?>(null)
suspend fun load() {
…
thumbnail = …
}
}
@Composable
fun Foo(…) {
val myClass = remember { MyClass(…) }
LaunchedEffect(Unit) { myClass.load() }
Text(myClass.thumbnail ?: "Not loaded yet")
}
CLOVIS
11/23/2023, 2:25 PMLaunchedEffect
that does all the workHamba
11/23/2023, 2:27 PMCLOVIS
11/23/2023, 2:28 PMLaunchedEffect
where you display the thumbnailHamba
11/23/2023, 2:29 PMHamba
11/23/2023, 2:29 PMHamba
11/23/2023, 2:29 PMHamba
11/23/2023, 2:33 PMHamba
11/23/2023, 2:34 PMFrancesc
11/23/2023, 4:49 PMLaunchedEffect(key1 = image) {
if (image != null) { /* load */ }
}
Daniel Pitts
11/23/2023, 5:17 PMclass MyClass(private val value: Value) {}
suspend fun MyClass(valueSource: Any) =
MyClass(expensiveSuspendFunction(valueSource))
andriyo
11/23/2023, 6:14 PMCLOVIS
11/23/2023, 10:21 PMDaniel Pitts
11/23/2023, 10:52 PMHamba
11/24/2023, 2:13 AMHamba
11/24/2023, 2:14 AMDaniel Pitts
11/24/2023, 2:37 AMCLOVIS
11/24/2023, 9:12 AMload
function), you'll see it's essentially the same, so you're good on that front.
The important question there is whether loadThumbnail
is blocking or not. But since it's suspend
, I'm going to assume it comes from a well-known library and they did it correctly. If you ever get blocking warnings from that function, do ask another question about it.
If this is on JVM, you probably just want a regular thread.I completely disagree. This is UI-triggered work, where you can't know if the user will navigate away / do something else that makes the result irrelevant. This is what structured concurrency is for.
If this isn’t on the JVM, then I’m not sure, but it might be some other mechanism to offload the expensive work.It's always threads, when they exist. On JS, there are no threads, so everything runs concurrently in the main thread.
If that “expensive” work is any kind of blocking task (a tight for loop or blocking io) then coroutines aren’t likely to help the way you want.Coroutines are just syntax sugar over thread pools, that allow cancellation and nice usage. They solve this problem exactly as well as thread pools do, it's the same underlying mechanism.
Hamba
11/24/2023, 9:35 AMHamba
11/24/2023, 9:36 AMCLOVIS
11/24/2023, 10:06 AMwhen you say blocking, do you mean multiple threads running the same function simultaneously would block each other?No, blocking means that the function stops the thread it is running on from doing anything else while it is running. Blocking may or may not be a problem depending on the thread that's blocked. For most threads, it's not an issue, but for the main thread it's a big nono: while it's blocked, it can't process user events, so your entire screen is frozen (no animations, no click events…). Coroutines makes this very easy, though: you can control in which threads operations execute. Here, you only have to replace your
load
function:
suspend fun load() {
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
thumbail = loadThumbnail()
}
}
<http://Dispatchers.IO|Dispatchers.IO>
is a thread pool that's made to be blocked by IO operations (reading files, network requests…). With this, you're guaranteed that the operation runs in that thread pool and not in the main thread. There is also Dispatchers.Default
for CPU operations (recursive functions, expensive math…), and a few others for other goals.Hamba
11/24/2023, 10:14 AMHamba
11/24/2023, 10:15 AMCLOVIS
11/24/2023, 10:16 AMLaunchedEffect
documentation doesn't say in which dispatcher the coroutines are started… You can log currentCoroutineContext()
inside your loading function to know what it usesHamba
11/24/2023, 10:16 AMCLOVIS
11/24/2023, 10:17 AMso adding the Dispatchers.IO ensures its run off the main threadyes
otherwise it probably would but isnt guaranteedI don't know. It depends on how
LaunchedEffect
is implemented, but I don't see it written in the documentation.
Maybe LaunchedEffect already adds <http://Dispatchers.IO|Dispatchers.IO>
or Dispatchers.Default
by itself? I don't knowHamba
11/24/2023, 10:18 AM