Alexander Maryanovsky
04/11/2022, 12:33 PMSequence
. How do I make this computation properly cancelable? All the Sequence functions are non-suspending, and the coroutine context is not available in a non-suspending function, so I can’t check for isActive
. Can I capture the coroutine context in the outermost (suspend) function and pass it into all the (non-suspending) sequence functions?Joffrey
04/11/2022, 12:36 PMsuspend
? That would probably be the easiest way. Making it suspend
and adding yield()
at the points where you want to check for cancellation (and allow interlacing with other suspend functions)Alexander Maryanovsky
04/11/2022, 12:46 PMfun compute(input: Sequence<T>){
return input
.map{ ... }
.reduce{ ... }
.groupBy{ ... }
// etc
}
suspend fun compute(input: Sequence<T>){
val context = coroutineContext
return input
.map{
if (!context.isActive)
throw CancellationException()
...
}
// etc
}
Joffrey
04/11/2022, 12:51 PMinline
, so you can use suspend functions insidesuspend fun compute(input: Sequence<T>){
return input
.map{ ... }
.forEach {
doStuffWith(it)
yield()
}
}
Alexander Maryanovsky
04/11/2022, 12:52 PMJoffrey
04/11/2022, 12:55 PMreduce
, which is also a terminal operator. Basically non-terminal operators like map
don't take any time because they just build a new Sequence with the given non-inlined lambda. So you can't call suspend functions there, but it's ok anyway because nothing really runs there.
However, when you use terminal operators like reduce
or forEach
they actually iterate the sequence and take time, but they are also inline - so you can use yield()
in themAlexander Maryanovsky
04/11/2022, 12:56 PMmap
can take time, if the mapping function itself is a long computationJoffrey
04/11/2022, 12:58 PMmap
itself doesn't take any time, but its lambda does when it's called, and it's only called when a terminal operator is called on the sequence. My point is that you only really need yield()
within loops (or any terminal operator) between each element, and that can be done by inserting it within the terminal operators' lambdas.Alexander Maryanovsky
04/11/2022, 12:59 PMmap
takes time..map{ computeItem(it, context) }
and the check for isActive
is in a loop in computeItem
Joffrey
04/11/2022, 1:05 PMinput
.map { longTransform1(it) }
.map { longTransform2(it) }
.forEach {
doStuffWith(it)
yield()
}
What will happen is the following:
for (e in input) {
val intermediate1 = longTransform1(e)
val intermediate2 = longTransform1(intermediate1)
doStuffWith(intermediate2)
yield()
}
Are you saying you would prefer to have cancellability even between each of those transformations? Like this:
for (e in input) {
val intermediate1 = longTransform1(e)
yield()
val intermediate2 = longTransform1(intermediate1)
yield()
doStuffWith(intermediate2)
yield()
}
Alexander Maryanovsky
04/11/2022, 1:05 PMlongTransform
Joffrey
04/11/2022, 1:06 PMFlow
instead of Sequence
, which is meant for this - same properties as sequences but with suspendable transformationsAlexander Maryanovsky
04/11/2022, 1:06 PMJoffrey
04/11/2022, 1:08 PMsuspend
and use flowsAlexander Maryanovsky
04/11/2022, 1:10 PMinput.asFlow()
.map{}
// etc.
Joffrey
04/11/2022, 1:10 PMAlexander Maryanovsky
04/11/2022, 1:11 PMyield
inside