【问题标题】:How can I wait the withContext in suspend function completes?如何等待挂起函数中的 withContext 完成?
【发布时间】:2021-10-10 04:57:12
【问题描述】:

我正在阅读 Google 文档中有关 Kotlin 协程的信息。建议我将 withContext(Dispacher.IO) 用于与主安全不同的线程。但我有一个问题,fetchData() 在服务器响应之前完成,所以 fetchData() 返回 null 结果。任何我感谢的帮助。

https://developer.android.com/kotlin/coroutines/coroutines-best-practices#main-safe

class GameRemoteDataSource @Inject constructor(val api : GameApi) {
    val IODispatcher: CoroutineDispatcher = Dispatchers.IO

    suspend fun fetchData() : Resource<ListGameResponse> {
        var resource : Resource<ListGameResponse> = Resource.loading(null)
        withContext(IODispatcher){
            Log.d("AAA Thread 1", "${Thread.currentThread().name}")
            api.getAllGame(page = 1).enqueue(object : Callback<ListGameResponse>{
                override fun onResponse(
                    call: Call<ListGameResponse>,
                    response: Response<ListGameResponse>
                ) {
                    if(response.code()==200){
                        resource = Resource.success(response.body())
                    }else{
                        resource = Resource.success(response.body())
                    }
                    Log.d("AAA code",response.code().toString())
                }
                override fun onFailure(call: Call<ListGameResponse>, t: Throwable) {
                    resource = Resource.error(t.message.toString(),null)
                    Log.d("AAA Thread", "${Thread.currentThread()}")
                }

            })
            Log.d("AAA Thread", "${Thread.currentThread()}")
            Log.d("AAA resource",resource.data.toString()+ resource.status.toString())
        }
        return resource
    }
}

【问题讨论】:

  • 这能回答你的问题吗? Existing 3-function callback to Kotlin Coroutines
  • 虽然该问题的答案可能被用来解决这个问题,但它并没有解释为什么 OP 的代码不起作用,并且在使用 Retrofit 时没有必要使用 suspendCoroutine,它已经内置暂停功能。

标签: android kotlin coroutine withcontext


【解决方案1】:

withContext 对于将带有回调的异步函数转换为可在协程中使用的挂起代码没有帮助。它更适用于转换同步阻塞代码。 this question 的答案中描述了您创建空变量并尝试将其填充到回调中以同步返回的无效策略。

对于带有回调的异步函数,如果它像上面的代码一样返回单个值,通常会使用suspendCoroutinesuspendCancellableCoroutine 将其转换为挂起函数。如果它随着时间的推移返回一系列值(多次调用回调),则适合使用callbackFlow 将其转换为可以在协程中收集的 Flow。

但是看起来你正在使用 Retrofit,它已经有一个替代 enqueue 的挂起功能,所以你不需要担心这一切。您可以改用await()awaitResponse() 函数。在这种情况下,await() 将返回 ListGameResponseawaitResponse() 将返回 Response&lt;ListGameResponse&gt;。所以awaitResponse()如果需要查看响应码比较好。

Awaiting 返回响应并在有错误时抛出异常,因此您可以使用 try/catch 而不是添加失败侦听器。

class GameRemoteDataSource @Inject constructor(val api : GameApi) {

    suspend fun fetchData(): Resource<ListGameResponse> {
        return try {
            val response = api.getAllGame(page = 1).awaitResponse()
            Log.d("AAA code", response.code().toString())
            Resource.success(response.body())
        } catch (exception: Exception) {
            Resource.error(exception.message.toString(),null)
        }
    }

}

【讨论】:

  • 您好,首先感谢您的帮助。就我而言,我想遵循 Google 文档中的建议,并且我想解决这个问题以便清楚地理解。我明白为什么它返回null。正如你上面提到的,“withContext 对转换异步函数没有帮助”所以我决定使用 execute() 而不是 enqueue() 因为 execute() 是同步函数。
  • 虽然execute 在理论上是可以接受的,但由于Retrofit 已经提供了await() 挂起功能,因此没有理由使用execute()。 Google 在文档中的建议仅适用于您使用不提供挂起功能的 API 时。使用withContext(Dispatchers.IO) { execute() } 可以,但绝对不是在这种情况下的最佳实践。它无缘无故地占用了一个额外的线程。
  • 请注意 Google 链接中的文字“如果一个类在协程中执行长时间运行的阻塞操作”。如果你使用await()awaitResponse() 挂起函数,它不是阻塞的,所以建议不适用。该段的重点是您的挂起函数不应该阻塞,这在 Kotlin 中是一个通用约定。
  • 正如您提到的“如果您使用 await() 或 awaitResponse() 挂起功能,它不会阻塞,因此建议不适用”。我想知道阻止当前线程启动新线程的“阻塞”? (在这种情况下,新线程是调用 api)
  • 阻塞意味​​着调用函数阻塞当前线程,就像execute一样。 enqueue 是非阻塞的,因为它不会阻塞当前线程,尽管它在内部使用另一个线程,具体取决于 VM 版本。而await 是非阻塞的,因为它暂停了。
【解决方案2】:

你应该使用suspendCancellableCoroutine将异步API转换成协程流,像这样

suspend fun fetchData(): ListGameResponse = withTimeout(Duration.seconds(60)) {
  suspendCancellableCoroutine<ListGameResponse> { cont ->
    api.getAllGame(page = 1).enqueue(object : Callback<ListGameResponse> {
      override fun onResponse(
          call: Call<ListGameResponse>,
          response: Response<ListGameResponse>
      ) {
        Log.d("AAA code", response.code().toString())
        cont.resume(response.body())
      }

      override fun onFailure(call: Call<ListGameResponse>, t: Throwable) {
        cont.resumeWithException(t)
      }
    })
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-15
    • 1970-01-01
    • 2020-05-22
    • 1970-01-01
    • 1970-01-01
    • 2023-01-31
    • 2020-11-14
    相关资源
    最近更新 更多