I have an API I have no control over.... which contains a method that does some work and returns the results asynchronously. I would like to call this method synchronously in parts of my application. I have done this by adding a class ResultHandler
which captures and returns the result. Is there a better way of doing this than the way I've done it below? Perhaps using standard kotlin (or Java as last resort) library methods. My preference would be for awaitReply
to return the result and also to remove the CountdownLatch
.
class Main {
companion object {
@JvmStatic
fun main(args: Array<String>) {
val result1 = Main().nonAsyncMethod1(arrayListOf(1, 2, 3, 4, 5))
result1.elements.forEach { println(it) }
}
}
class Result1(var elements: Collection<String>)
fun asyncMethod1(x: Collection<Int>, callback: (Result1) -> Unit) {
Thread().run {
// do some calculation
Thread.sleep(1000)
callback(Result1(x.map { "\"$it\"" }.toList()))
}
}
private fun nonAsyncMethod1(entities: Collection<Int>): Result1 {
val resultHandler = ResultHandler<Result1>()
awaitReply<Result1> {
asyncMethod1(entities, resultHandler)
}
return resultHandler.getResponse()
}
open class ResultHandler<T : Any> : (T) -> Unit {
private lateinit var response: T
private val latch = CountDownLatch(1)
override fun invoke(response: T) {
latch.countDown()
this.response = response
}
fun getResponse(): T {
latch.await()
return response
}
}
private fun <T : Any> awaitReply(call: () -> Unit) {
return call.invoke()
}
}
Coroutines are Kotlin features that allow you to surround your blocking calls(Synchronous) codes in non-blocking(Asynchronous) constructs. A coroutine is like a function or a call which by default runs Asynchronously in parallel with others.
The simplest way to execute a method asynchronously is to start executing the method by calling the delegate's BeginInvoke method, do some work on the main thread, and then call the delegate's EndInvoke method. EndInvoke might block the calling thread because it does not return until the asynchronous call completes.
Coroutines Kotlin's approach to working with asynchronous code is using coroutines, which is the idea of suspendable computations, i.e. the idea that a function can suspend its execution at some point and resume later on.
A CoroutineScope defines a lifecycle, a lifetime, for Coroutines that are built and launched from it. A CoroutineScope lifecycle starts as soon as it is created and ends when it is canceled or when it associated Job or SupervisorJob finishes.
Thanks to the hint from the_dani
I managed come to the solution below using coroutines as detailed in "Wrapping callbacks" section of the Kotlin coroutines documentation:
class Main {
companion object {
@JvmStatic
fun main(args: Array<String>) = runBlocking {
val result1 = Main().nonAsyncMethod1(arrayListOf(1, 2, 3, 4, 5))
result1.elements.forEach { println(it) }
}
}
class Result1(var elements: Collection<String>)
fun asyncMethod1(x: Collection<Int>, callback: (Result1) -> Unit) {
Thread().run {
// do some calculation
Thread.sleep(1000)
callback(Result1(x.map { "\"$it\"" }.toList()))
}
}
suspend fun nonAsyncMethod1(entities: Collection<Int>): Result1 = suspendCoroutine {
cont ->
asyncMethod1(entities) { cont.resume(it) }
}
}
You can wrap async functions with a callback with coroutines (Coroutines are similar to C# async/await, you can create asynchronous code that looks very much synchronous, but which is executed asynchronous)
https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md#wrapping-callbacks
There is a simple pattern. Assume that you have someLongComputation function with callback that receives some Value that is a result of this computation.
fun someLongComputation(params: Params, callback: (Value) -> Unit)`
You can convert it into a suspending function with the following straightforward code:
suspend fun someLongComputation(params: Params): Value = suspendCoroutine { cont -> someLongComputation(params) { cont.resume(it) } }
Suspend functions can only be called in a coroutine context (for example with launch{ }
), but in order to wait you can use runBlocking{ }
which should then wait for the coroutine to finish. This should create your desired behaviour.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With