https://kotlinlang.org logo
#coroutines
Title
# coroutines
l

Landry Norris

01/17/2023, 6:22 PM
Should
isActive
return true if I
launch
, but the dispatcher hasn’t picked up the job yet? I launch the jobs on a singleThreadContext and there’s no delay or suspension in the job, so I’d assume only one runs at a time. When launching a new job, I want to cancel the last one if it hasn’t started yet, so I have
if(latestSeek?.isActive != true) latestSeek?.cancel()
, but the jobs seem to pile up anyways. Each job overwrites the result of the previous job, so any tasks between the last running and the next job is wasted time.
Requirements are essentially: 1. Must be multiplatform (coroutines is good for this) 2. Jobs must always run on the same Thread that is not main (limitation placed by a C library we use via JNI on Android and cinterop on iOS) 3. If a job is started, we want to avoid cancelling it, or the C library becomes unhappy 4. We want to cancel any jobs that haven’t started yet when there’s a newer job since each job takes a non-negligible amount of time.
k

kevin.cianfarini

01/17/2023, 6:47 PM
Why do you only want to launch if the last job has been dispatched versus just always canceling the last job? You could potentially use
CoroutineStart.UNDISPATCHED
here but idk if thats what you need.
What you describe is essentially just denouncing
l

Landry Norris

01/17/2023, 6:48 PM
If I cancel the last job, will it stop execution, or only if there’s suspension points? I want it to finish because the C library will crash if a started job doesn’t complete.
k

kevin.cianfarini

01/17/2023, 6:50 PM
Ah I see. I have a similar need here. Canceling won't interrupt a blocking native call. https://github.com/Kotlin/kotlinx.coroutines/issues/3563
You should look into undispatched coroutine start to ensure that the C call has always been started if the job is active
l

Landry Norris

01/17/2023, 7:00 PM
Looks like launch(start = UNDISPATCHED) is blocking until the first suspension point.
k

kevin.cianfarini

01/17/2023, 7:01 PM
If the underlying C call is blocking there’s nothing you can do about that
Oh wait that makes sense
sorry
It’s blocking the caller you mean
l

Landry Norris

01/17/2023, 7:01 PM
I want the C call to run on the singleThread, not the thread that called launch
Do I want the LAZY start?
k

kevin.cianfarini

01/17/2023, 7:02 PM
Copy code
launch(start = UNDISPATCHED) { 
  withContext(singleThread) { cFunction() }
}
probably won’t help though because withContext will suspend
l

Landry Norris

01/17/2023, 7:04 PM
When I try LAZY, nothing ever runs.
k

kevin.cianfarini

01/17/2023, 7:04 PM
I would think lazy is a go?
l

Landry Norris

01/17/2023, 7:05 PM
Presumably, the mechanism can track that a job hasn’t been picked up by the dispatcher.
k

kevin.cianfarini

01/17/2023, 7:05 PM
Copy code
* By default, the coroutine is immediately scheduled for execution.
 * Other start options can be specified via `start` parameter. See [CoroutineStart] for details.
 * An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case,
 * the coroutine [Job] is created in _new_ state. It can be explicitly started with [start][Job.start] function
 * and will be started implicitly on the first invocation of [join][Job.join].
Are you calling
start
?
l

Landry Norris

01/17/2023, 7:06 PM
I am not. Not sure when to call start. If I call it right after the launch, presumably, it will be the same as before.
I’m not sure why jobs always start in the active state. What would be helpful in this case would be for a Job to start as NEW, then get set to ACTIVE when it actually starts executing on the correct dispatcher.
f

franztesca

01/17/2023, 7:16 PM
You can do:
Copy code
val job =launch(singleThreadDispatcher) { withContext(NonCancellable) { expensiveTask() } }
And then
job.cancelAndJoin
will return immediately if the task is not started, or wait until the completion of the expensive task otherwise.
l

Landry Norris

01/17/2023, 7:17 PM
Is there a way to check if the CoroutineContext is running a job right now? Since it’s SingleThreadDispatcher, only one job will happen at once.
Will
cancelAndJoin
block the thread I call launch on?
f

franztesca

01/17/2023, 7:20 PM
Alternative: usa a conflated channel (or stateflow) to run only the most recent task
Copy code
val tasksToDo = MutableStateFlow<suspend () -> Unit>

myScope.launch(singleThreadDispatcher) {
    taskToDo.collect {
        it.invoke()
    }
}
l

Landry Norris

01/17/2023, 7:21 PM
I’ll try that out.
Where’s the best place to open a YouTrack requesting a way to check if a job has been picked up by the dispatcher or not?
k

kevin.cianfarini

01/17/2023, 7:26 PM
cancalAndJoin
will block until the underlying blocking C call returns. Note that cancellation will have no impact on the C call unless you explicitly check for it somehow.
n

Nick Allen

01/19/2023, 5:47 AM
Calling
launch
multiple times is for starting concurrent coroutines. That doesn't match your use case. Keep in mind launched code does not always run in the same order it was launched. If you want things to run one after another, you want a single coroutine as is described a few comments up though definitely use a conflated channel. channel = queue, MutableStateFlow = observable value. MutableStateFlow will reference the task even after it's finished running.
3 Views