I'm running a task with a delay that I might wanna...
# coroutines
r
I'm running a task with a delay that I might wanna cancel if another code path is taken by a new invocation of that function. How should I handle that? So far I'm using a
SupervisorJob
and
scope.launch(job) { delay(...); ... }
and then
job.cancelChildren()
to cancel the delayed functions. Any better options?
c
You can use a regular Job and just call
cancel
on it
Copy code
fun CoroutineScope.foo() {
    val other = Job(coroutineContext.job)
    launch(other) {
        delay(…)
        …
    }

    …
    if (…) {
        other.cancel()
    }
}
r
Will that
launch
dispatch properly, or hang something somewhere while it's `delay()`ing?
... had some fun experiences that way
Or was that only in conjuction with
coroutineScope { ... }
c
As always, the behavior of
launch
depends on the Dispatcher, but this code is the recommended way to do these kinds of things
r
aye
What are the behaviors of the dispatcher on
lifecycleScope
on android, and
<http://Dispatcher.IO|Dispatcher.IO>
? Just to know some defaults
c
now that I think about it though
Copy code
fun CoroutineScope.foo() {
    val other = launch {
        delay(…)
        …
    }

    …
    other.cancel()
}
is better because you don't have to declare the job yourself (but is otherwise identical)
r
I gotta bring
other
outside of this scope, as another invocation of
foo()
wants to cancel
other
c
Ah, so each time
foo()
is called, the previous call is cancelled?
r
Sometimes
c
First, there's probably a way to make this algorithm way simpler by writing it the other way around. For example, a
Flow<T>
that only emits the latest value but otherwise, just store the
Job
that
launch
creates and call
cancel
on it. If you store it as a local variable, do remember to
Mutex
it though
^btw this is basically how
LaunchedEffect
works
r
This is the full code, as it is now. There's a few more branches with different opcodes. I could probably clean up everything to make it into a proper
Flow
with
collectLatest()
🤔
Copy code
val transitionJob = SupervisorJob(lifecycleScope.coroutineContext.job)

        lifecycleScope.launch {
            hdmiService.events().collect { message ->
                if (message.opcode != Constants.MESSAGE_REPORT_POWER_STATUS) {
                    Timber.tag("CEC").d { message.toString() }
                }
                when (message.opcode) {
                    Constants.MESSAGE_REPORT_POWER_STATUS -> {
                        if (message.source == <http://HdmiLogicalAddress.TV|HdmiLogicalAddress.TV>) {
                            val result: TVState =
                                when (val powerStatus = message.params.firstOrNull()?.toInt()) {
                                    HdmiControlManager.POWER_STATUS_ON -> {
                                        // TODO
//                                        transitionJob.cancelChildren()
                                        TVState.On
                                    }

                                    HdmiControlManager.POWER_STATUS_STANDBY -> {
                                        transitionJob.cancelChildren()
                                        TVState.Standby
                                    }

                                    HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON -> {
                                        transitionJob.cancelChildren()
                                        val heuristics = cecStateDetection?.tvTurnedOnDetectionHeuristics
                                        Timber.d { "Heuristics is $heuristics" }
                                        if (heuristics is TurnedOnDetectionHeuristics.DelayedAfterTransitionToOn) {
                                            launch(transitionJob) {
                                                Timber.d { "Delaying ${heuristics.delay} for TVState.On" }
                                                delay(heuristics.delay)
                                                Timber.d { "Delayed for TVState.On" }
                                                cecFlows.tvState.emit(TVState.On)
                                            }
                                        }
                                        TVState.TransientToOn
                                    }

                                    HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY -> {
                                        transitionJob.cancelChildren()
                                        val heuristics = cecStateDetection?.tvTurnedOffDetectionHeuristics
                                        if (heuristics is TurnedOffDetectionHeuristics.TransitionToStandby) {
                                            launch(transitionJob) {
                                                delay(1.seconds)
                                                cecFlows.tvState.emit(TVState.Standby)
                                            }
                                        }
                                        TVState.TransientToStandby
                                    }

                                    HdmiControlManager.POWER_STATUS_UNKNOWN -> {
                                        transitionJob.cancelChildren()
                                        TVState.Unknown
                                    }

                                    else -> {
                                        transitionJob.cancelChildren()
                                        Timber.tag(TAG).w("Unknown response to query display status: $powerStatus")
                                        TVState.Unknown
                                    }
                                }
                            // TODO for testing purposes
                            if (result != TVState.On) {
                                cecFlows.tvState.emit(result)
                            }
                        }
                    }
So you'd split off the opcodes into different flows, and then use the one based on the power status to
collectLatest()
or similar?
c
Honestly, I don't really know, and it's a big block of code 😅 At the very least,
.cancel()
on the result of a
.launch()
seems to be what you want
r
What makes using the result of a
launch()
different than attaching it to a
job
via
launch(job)
?
c
it's shorter to write
it's just that
launch()
internally already creates a child job of the current scope, so you can use it instead of creating your own, but other than it's identical