Title
a

Alexander Maryanovsky

04/11/2022, 12:33 PM
I have a long computation in a non-suspending function which consists of several transformations (map, reduce etc.) on a
``Sequence``
. 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?
j

Joffrey

04/11/2022, 12:36 PM
Is it an option for you to make this non-suspending function
``suspend``
? That would probably be the easiest way. Making it
``suspend``
``yield()``
at the points where you want to check for cancellation (and allow interlacing with other suspend functions)
a

Alexander Maryanovsky

04/11/2022, 12:46 PM
I can make the main function itself suspend, sure. The problem is with the sequence functions. It looks like this:
``````fun compute(input: Sequence<T>){
return input
.map{ ... }
.reduce{ ... }
.groupBy{ ... }
// etc
}``````
The sequence functions don’t take suspending functions, they take regular functions.
My idea is to do this:
``````suspend fun compute(input: Sequence<T>){
val context = coroutineContext
return input
.map{
if (!context.isActive)
throw CancellationException()
...
}
// etc
}``````
j

Joffrey

04/11/2022, 12:51 PM
Which terminal operator are you using? Usually the terminal operator is
``inline``
, so you can use suspend functions inside
``````suspend fun compute(input: Sequence<T>){
return input
.map{ ... }
.forEach {
doStuffWith(it)
yield()
}
}``````
a

Alexander Maryanovsky

04/11/2022, 12:52 PM
Hmm
That’s a good point, but if the intermediate operators take all the time, and the terminal operator is only run at the end, that may not help
j

Joffrey

04/11/2022, 12:55 PM
Same goes with
``reduce``
, 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 them
a

Alexander Maryanovsky

04/11/2022, 12:56 PM
``map``
can take time, if the mapping function itself is a long computation
j

Joffrey

04/11/2022, 12:58 PM
``map``
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.
a

Alexander Maryanovsky

04/11/2022, 12:59 PM
I understand, but in my case the lambda I pass to
``map``
takes time.
The example code I wrote may be confusing. The actual code is more like
``.map{ computeItem(it, context) }``
and the check for
``isActive``
is in a loop in
``computeItem``
j

Joffrey

04/11/2022, 1:05 PM
If I understand correctly, you mean that allowing interlacing and cancellation between each element is not sufficient for you, and you would like to cancel between each operation even on the same element? If you do:
``````input
.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()
}``````
a

Alexander Maryanovsky

04/11/2022, 1:05 PM
Even inside
``longTransform``
j

Joffrey

04/11/2022, 1:06 PM
Then I think you should just use
``Flow``
``Sequence``
, which is meant for this - same properties as sequences but with suspendable transformations
1
a

Alexander Maryanovsky

04/11/2022, 1:06 PM
Interesting idea
j

Joffrey

04/11/2022, 1:08 PM
I mean, if you even need cancellability in the transform functions, probably you should make those transform functions
``suspend``
and use flows
a

Alexander Maryanovsky

04/11/2022, 1:10 PM
yes, you’re right
I’ll just do
``````input.asFlow()
.map{}
// etc.``````
👍 1
j

Joffrey

04/11/2022, 1:10 PM
Yep exactly
a

Alexander Maryanovsky

04/11/2022, 1:11 PM
and make the computations suspend functions with
``yield``
inside
awesome, thanks!