Make asynchronous call synchronous in Kotlin

六眼飞鱼酱① 提交于 2020-12-01 11:03:33

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!