问题
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()
}
}
回答1:
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) }
}
}
回答2:
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.
来源:https://stackoverflow.com/questions/53765396/make-asynchronous-call-synchronous-in-kotlin