在 Kotlin 的 suspendCoroutine 中,AndroidNetworking 从不 returns

AndroidNetworking never returns when in suspendCoroutine in Kotlin

我正在试验新的协同程序,并尝试将它们合并到一个现有的项目中,其中包含一些丑陋的 AndroidNetworking api 请求。

所以我当前的 api 请求正在使用这样的回调...

fun getSomethingFromBackend(callback: (response: JSONArray?, error: String?) -> Unit) {
  AndroidNetworking.get(...).build().getAsJSONObject(object : JSONObjectRequestListener {
    override fun onResponse(response: JSONObject) { ... }
    override fun onError(error: ANError) { ... }
  }
}

现在我正在尝试合并这样的协程...

fun someFunction() = runBlocking {
  async { callGetSomething() }.await()
}

suspend fun callGetSomething(): JSONObject? = suspendCoroutine {
  Something().getSomethingFromBackend {something
    ...
    it.resume(something)
  }
}

如您所见,我正在尝试 运行 协程中的 callGetSomething,一旦到了那里,暂停协程直到异步 API returns。

我的问题是,它从来没有 returns。即使我在 onResponseonError 等中调试,它也永远不会从后端返回。

但是如果我删除 = suspendCoroutine 它可以工作,至少它 returns,但是 await 不会工作,因为执行将继续。

如何解决这个问题,让 await() 实际上等待异步调用回调?

非常感谢!

尝试这样的事情:

fun someFunction() {
    val jsonObject: JSONObject? = runBlocking {
        callGetSomething()
    }
    // do something with jsonObject
}

suspend fun callGetSomething(): JSONObject? = suspendCoroutine {
    Something().getSomethingFromBackend { response, error ->
        it.resume(response)
    }
}

您的主要问题是您正在尝试使用 runBlocking,这会抵消您为项目引入异步网络库和协程所做的所有工作。如果你真的想在等待结果时阻塞,那么就使用阻塞网络调用。

如果您想继续使用异步网络操作,那么忘记您的阻塞 someFunction 并直接使用 launch 块中的 callGetSomething

override fun onSomeEvent(...) {
   this.launch {
     val result = callGetSomething(...)
     // use the result, you're on the GUI thread here
   }
}

以上假定您的 thisActivityFragmentViewModel 或实现 CoroutineScope 的类似对象。查看其 documentation 了解如何实现。

好的,谢谢大家的帮助。 我将解释我是如何发现问题以及如何解决问题的。

在研究了更多有关回调和协程的信息后,我决定在回调期间记录线程名称,就像这样。 Log.d("TAG", "Execution thread: "+Thread.currentThread().name).

这让我意识到所有的回调(错误和成功),实际上 运行 在主线程上, NOT 在协程上为它设置。

所以我协程中的代码在 D/TAG: Execution thread: DefaultDispatcher-worker-1 中运行,但回调在 D/TAG: Execution thread: main 中运行。

解决方法

  1. 避免runBlocking:这会阻塞主线程,在这种情况下,这是回调永远不会返回的原因。

  2. 在启动协程时使用Dispatchers.Main,所以我实际上可以在UI.

  3. 中使用返回值
  4. 使用 suspendCoroutineresume()resumeWithException(感谢@Sergey)

例子

fun someFunction() {
  GlobalScope.launch(Dispatchers.Main) {
    result = GlobalScope.async { callGetSomething() }.await()
    ...
  }
}

suspend fun callGetSomething(): JSONObject? = suspendCoroutine {
  Something().getSomethingFromBackend {something
    ...
    it.resume(something)
  }
}