JVM specific additions
part of kovenant-jvm
Executors
It's likely that your project already uses threadpools or that you want to leverage the threadpool Kovenant uses. Because too many threads just leads to context switching and thus performance degradation. Therefore Kovenant provides some facilities for interoperability with Java's Executors.
- To convert Kovenant's
Dispatchers to Java'sExecutoruse the extension functionasExecutor() - To convert Kovenant's
Dispatchers to Java'sExecutorServiceuse the extension functionasExecutorService() - To convert Java's
ExecutororExecutorServiceto Kovenant'sDispatcheruse the extension functionasDispatcher()
fun main(args: Array<String>) {
val executorService = Kovenant.context.workerContext.dispatcher.asExecutorService()
val tasks = listOf(*(Array(5) { FibCallable(25 - it) }))
val (n, fib) = executorService.invokeAny(tasks)
println("invokeAny: fib($n) = $fib")
println()
val results = executorService.invokeAll(tasks)
results forEach { future ->
val (i, res) = future.get()
println("invokeAll: fib($i) = $res")
}
//Not necessary but shuts down a bit quicker
executorService.shutdownNow()
}
private class FibCallable(private val n: Int) : Callable<Pair<Int, Int>> {
override fun call() = Pair(n, fib(n))
}
Throttle
There are case where finer control of the number of parallel tasks is needed, for instance when some specific type of task uses vast amounts of memory. This is what a Throttle is for. It allows you to limit the number of parallel task completely independent of any underlying Dispatcher. So no matter on what Context and thus Dispatcher your async tasks run, it's always within bounds.
You create a Throttle instance simply calling its constructor with any positive number indicating the maximum number of concurrent tasks:
// a Throttle with 2 parallel tasks at max
val myThrottle = Throttle(2)
Optionally you can provide the Context on which new async tasks are run on by default. The can always be overridden per specific task.
//configure with myContext
//the default is Kovenant.context
val myThrottle = Throttle(2, myContext)
simple tasks
The easiest way to use a Throttle instance is by using the task method. This creates a task similar to the general task method that gets scheduled somewhere in the future. The result is, of course, a Promise
myThrottle.task {
foo()
} always {
println("done")
}
manual registering
Sometimes you want to throttle a whole chain of promises. So you need to manually register the start and end of the chain. The registerTask and registerDone gives you that freedom. It's up to you to make sure that every registerTask is balanced with a countering registerDone. Failing to do so may either result in more than the configured tasks to run parallel or simply a deadlock.
val promise = myThrottle.registerTask { foo() }
//the rest of the chain
val lastPromise = promise then { bar() } then { baz() }
myThrottle.registerDone(lastPromise)
Full Throttle example
fun main(args: Array<String>) {
Kovenant.context {
workerContext.dispatcher { concurrentTasks = 8 }
}
val sleepThrottle = Throttle(4)
val snoozeThrottle = Throttle(2)
val promises = (1..10).map { number ->
sleepThrottle.task {
Thread.sleep(1000)
number
} success {
println("#$it sleeper awakes")
}
}
promises.forEach { promise ->
val registeredPromise = snoozeThrottle.registerTask(promise) {
Thread.sleep(700)
}
val finalPromise = registeredPromise then {
"#${promise.get()} snoozing"
}
snoozeThrottle.registerDone(finalPromise)
finalPromise success {
println(it)
}
}
println("all tasks created")
}