Kotlin: Is there a tool that allows me to control parallelism when executing suspend functions? - kotlin

I'm trying to execute certain suspend function multiple times, in such a way that never more than N of these are being executed at the same time.
For those acquainted with Akka and Scala Streaming libraries, something like mapAsync.
I did my own implementation using one input channel (as in kotlin channels) and N output channels. But it seems cumbersome and not very efficient.
The code I'm currently using is somewhat like this:
val inChannel = Channel<T>()
val outChannels = (0..n).map{
Channel<T>()
}
launch{
var i = 0
for(t in inChannel){
outChannels[i].offer(t)
i = ((i+1)%n)
}
}
outChannels.forEach{outChannel ->
launch{
for(t in outChannel){
fn(t)
}
}
}
Of course it has error management and everything, but still...
Edit: I did the following test, and it failed.
test("Parallelism is correctly capped") {
val scope = CoroutineScope(Dispatchers.Default.limitedParallelism(3))
var num = 0
(1..100).map {
scope.launch {
num ++
println("started $it")
delay(Long.MAX_VALUE)
}
}
delay(500)
assertEquals(3,num)
}

You can use the limitedParallelism-function on a Dispatcher (experimental in v1.6.0), and use the returned dispatcher to call your asynchronous functions. The function returns a view over the original dispatcher which limits the parallelism to a limit you provide. You can use it like this:
val limit = 2 // Or some other number
val dispatcher = Dispatchers.Default
val limitedDispatcher = dispatcher.limitedParallelism(limit)
for (n in 0..100) {
scope.launch(limitedDispatcher) {
executeTask(n)
}
}

Your question, as asked, calls for #marstran's answer. If what you want is that no more than N coroutines are being actively executed at any given time (in parallel), then limitedParallelism is the way to go:
val maxThreads: Int = TODO("some max number of threads")
val limitedDispatcher = Dispatchers.Default.limitedParallelism(maxThreads)
elements.forEach { elt ->
scope.launch(limitedDispatcher) {
doSomething(elt)
}
}
Now, if what you want is to even limit concurrency, so that at most N coroutines are run concurrently (potentially interlacing), regardless of threads, you could use a Semaphore instead:
val maxConcurrency: Int = TODO("some max number of concurrency coroutines")
val semaphore = Semaphore(maxConcurrency)
elements.forEach { elt ->
scope.async {
semaphore.withPermit {
doSomething(elt)
}
}
}
You can also combine both approaches.

Other answers already explained that it depends whether you need to limit parallelism or concurrency. If you need to limit concurrency, then you can do this similarly to your original solution, but with only a single channel:
val channel = Channel<T>()
repeat(n) {
launch {
for(t in channel){
fn(t)
}
}
}
Also note that offer() in your example does not guarantee that the task will be ever executed. If the next consumer in the round robin is still occupied with the previous task, the new task is simply ignored.

Related

Need to start a code line after react subscribe() functions end

val totalNumInst = TotalNumObj()
devSupportService.sendAllTalktalkMessages(naverId)
devSupportService.sendAllAutoDepositTalktalkMessages(naverId, totalNum)
logger.info("${totalNumInst.totalNum}")
Mono<>
.doOnSuccess { }
.subscribe()
First two lines execute several Mono<>.subscribe() functions. In each Mono<>'s .doOnSuccess{} the totalNum variable is increasing. At the last line, I added a log which shows totalNum. But the totalNum variable always shows the initial value, 0.
I need to leave a log which shows how many times does the Mono<>.subscribe() is executed.
Thank you for reading my question.
There are 2 ways of solving your issue. The blocking and non-blocking.
Blocking
create a countDownLatch, pass it to sendAllTalktalkMessages and sendAllAutoDepositTalktalkMessages, then wait for it being latched
val totalNumInst = TotalNumObj()
val latch = CountDownLatch(2)
devSupportService.sendAllTalktalkMessages(naverId, totalNumInst, latch)
devSupportService.sendAllAutoDepositTalktalkMessages(naverId, totalNumInst, latch)
if (!latch.await(30, TimeUnit.SECONDS)) {
throw TimeoutException("Waiting timed out")
}
logger.info("${totalNumInst.totalNum}")
And add latch.countDown() to each doOnSuccess (but I'd recommend to countDown in doFinally in case of the chain sending error signal)
Mono<>
.doOnSuccess { latch.countDown() }
.subscribe()
This is blocking solution, it is against reactive non-blocking concept.
Non-blocking
Make sendAllTalktalkMessages and sendAllAutoDepositTalktalkMessages returning Mono and zip them (moreover in that case you don't need to pass totalNumInst to them)
Mono.zip(
devSupportService.sendAllTalktalkMessages(naverId)
.map { 1 }
.onErrorResume { Mono.just(0) }
.defaultIfEmpty(0),
devSupportService.sendAllAutoDepositTalktalkMessages(naverId)
.map { 1 }
.onErrorResume { 0 }
.defaultIfEmpty(0)
) { counter1, counter2 -> counter1 + counter2 }
.subscribe { totalNum -> logger.info("$totalNum") }
in this realisation you count each success as 1 and each error or empty signal as 0.

How do I use recursion with kotlin coroutines with yield

im trying to experiment in kotlin using kotlins yield functionality.
This function should return the union of two sequences, and looks like this:
fun <T>Sequence<T>.union(seq : Sequence<T>): Sequence<T> = sequence {
if (any()) {
if (seq.contains(first())) {
yield(first())
}
drop(0).union(seq)
}
}
When I run this function with these two sequences:
val seq2 = (1..30).asSequence()
val seq3 = (1..32).asSequence()
It just returns a sequence of [1], it doesnt do any recursive calls at all. What am I doing wrong, why is the recursive union() call not activated at all?

Kotlin: maxBy{} with optimum-value

Let's say I have the following code in Kotlin:
val min = listOf("hello", "", "teeeeeest").minBy { it.length }
What I understand from the implementation of minBy is that it tracks minValue in a variable and iterates through the whole collection and updates it once it finds an even smaller element.
In the case of Strings though, we know that no element can have a value smaller than 0, therefore the empty String "" is optimal and the iteration can be stopped.
Is there a way I can tell minBy (or maxBy) the optimal value so it can stop once that is reached? If not, how can I implement this most easily?
There's no function in the stdlib that can do this, but you can implement it as an extension function yourself.
By using the non-local return feature of inline lambda functions in Kotlin, you can implement it like this:
fun <T, E : Comparable<E>> Iterable<T>.minBy(theoreticalMinimum: E, keySelector: (T) -> E): T? =
minBy {
val key = keySelector(it)
if (key <= theoreticalMinimum) return it // Non-local return.
else key
}
Now you can use it like this, and it will never visit "teeeeeest":
val min = listOf("hello", "", "teeeeeest").minBy(theoreticalMinimum = 0) { it.length }

Check if value is not equal in when()-statement

Is it possible to test a String for not being equal in a when-statement?
This is of course perfectly possible with a simpel if statement:
val storedValue = sharedPreferences.getString(identifier, NOT_SET)
if (storedValue != NOT_SET) {
super.setValue(storedValue)
}
However, I like how storedValue is scoped inside of the when-statement in this snippet:
when (val storedValue = sharedPreferences.getString(identifier, NOT_SET)) {
NOT_SET -> {}
else -> super.setValue(storedValue)
}
The downfall is the empty code block for the NOT_SET entry.
Is it possible to combine these two?
I'd like to scope storedValue and get rid of empty code blocks. The result would be comparable to:
when (val storedValue = sharedPreferences.getString(identifier, NOT_SET)) {
!NOT_SET -> super.setValue(storedValue)
}
Since SharedPreferences is part of the Android framework, this would another solution:
if (sharedPreferences.contains(identifier)) {
super.setValue(sharedPreferences.getString(identifier, null))
}
However, the goal of my question is deeper understanding of the possibilities of Kotlin, for the sake of learning.
As mentioned in the comments, negation is not directly supported like this in when statements (yet) even in Kotlin.
The most idiomatic way at the moment most probably is like:
val storedValue = sharedPreferences.getString(identifier, NOT_SET)
when {
storedValue != "NOT_SET" -> super.setValue(storedValue)
}
Another working variant utilizing !in in when could be for example:
when (val storedValue = sharedPreferences.getString(identifier, NOT_SET)) {
!in setOf("NOT_SET") -> super.setValue(storedValue)
}
And as both != and!in will compare case sensitively, so would make sense to get the string like sharedPreferences.getString(identifier, NOT_SET).toUpperCase(), or use equalsIgnoreCase in the first variant.

How to create an infinitely long sequence in Kotlin

I'm looking for something like
val allInts = (1..).asSequence()
so I could, for example
allInts.take(5)
val sequence = generateSequence(1) { it + 1 }
val taken = sequence.take(5);
taken.forEach { println(it) }
This is not really infinite, though: it will overflow when Integer.MAX_VALUE is reached.
If you need an infinite sequence you should use the new sequence function:
val sequence = sequence {
while (true) {
yield(someValue())
}
}
Previous answer
Use Int.MAX_VALUE as the upper bound. You cannot have an integer greater than Int.MAX_VALUE.
val allInts = (1..Int.MAX_VALUE).asSequence()
JB's answer is good but you could also go with
generateSequence(1, Int::inc)
if you're into the whole brevity thing.