How to run suspend method via reflection?

2019-07-17 10:36发布

问题:

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 

        }
    }
}

回答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.