How to run suspend method via reflection?
There is an coroutine block that can runs suspend functions.
But I call the function by invoke
via reflection. This is java style invocation, apparently a simple call will not work.
Are there ways to run reflected method asynchronously? How to await this method?
import kotlin.coroutines.experimental.*
class TestClass(val InString: String) {
suspend fun printString() {
println(InString)
}
}
fun launch(context: CoroutineContext, block: suspend () -> Unit) =
block.startCoroutine(StandaloneCoroutine(context))
private class StandaloneCoroutine(override val context: CoroutineContext): Continuation<Unit> {
override fun resume(value: Unit) {}
override fun resumeWithException(exception: Throwable) {
val currentThread = Thread.currentThread()
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
}
}
fun main(args: Array<String>) {
launch(EmptyCoroutineContext) {
val a = TestClass("TestString");
for (method in a.javaClass.methods) {
if (method.name == "printString")
method.invoke(a) // Exception in thread "main" java.lang.IllegalArgumentException: wrong number of arguments
}
}
}
Solution 1:
Update
Since Kotlin 1.3 reflection natively supports calling suspending functions via KFunction.callSuspend
and KFunction.callSuspendBy
, so the above workaround is no longer needed.
Original Answer
Every suspend
method in Kotlin is represented on JVM via CPS transformation that is explained in the coroutines design document. Java reflection is not aware about it and Kotlin reflection does not currently provide convenient means to perform invocation of suspending function either.
You'll have to do invocation with CPS transformation yourself via helper function. I'd suggest to implement the following helper for this purpose:
import java.lang.reflect.Method
import kotlin.coroutines.experimental.intrinsics.*
suspend fun Method.invokeSuspend(obj: Any, vararg args: Any?): Any? =
suspendCoroutineOrReturn { cont ->
invoke(obj, *args, cont)
}
Now if you replace invoke
with invokeSuspend
in your code, then it is going to work just like expected.
Solution 2:
You can use Kotlin reflection and call suspend
as follows:
suspend fun Any.invokeSuspendFunction(methodName: String, vararg args: Any?): Any? =
this::class.memberFunctions.find { it.name == methodName }?.also {
it.isAccessible = true
return it.callSuspend(this, *args)
}