Kotlin 协程 lockup/freezing

Kotlin Coroutines lockup/freezing

我在 运行 宁协程时遇到奇怪的锁定(冻结)。有时它工作得很好,有时它会等待并在很长时间后继续 运行ning(或根本不)。我不知道如果我的设置不正确,存在一些竞争条件,或者我可能误解了协程应该如何使用。应用程序本身不会冻结或崩溃,它只是协程 stop/pause 执行。

例如,在这一点中 scope.launch 有时不会继续执行到 billingClientDeferred.await()

    override suspend fun getPurchases(): Result<List<Purchase>> = suspendCoroutine { continuation ->
    scope.launch {
        billingClientDeferred.await().success { client ->
            client.queryPurchasesAsync(BillingClient.SkuType.SUBS) Subs@{ billingResultSubs, purchasesSubs ->
                if (billingResultSubs.responseCode != BillingClient.BillingResponseCode.OK) {
                    continuation.resume(Result.Failure.Unknown())
                    return@Subs
                }
                continuation.resume(Result.Success(purchasesSubs))
            }
        }
    }
}

scope 声明为:

private val scope = CoroutineScope(Job() + Dispatchers.Default)

然后从WalletManager

调用
override suspend fun verifyProducts(): Result<Unit> = billingProvider.getPurchases()
        .successSuspend { purchases ->
            purchases.forEach {
                activatePurchase(it)
            }
        }.map { }

最后是从这样的ViewModel中调用的

override fun verify() {
    viewModelScope.launch {
        userSupervisor.walletManager.mapResult {
            it.verifyProducts()
        }
    }
}

我猜我使用的调度程序和范围的组合存在一些问题。我正在尝试使用 Dispatchers.DefaultDispatchers.Main、使 class 符合 CoroutineScope 的不同方法,使用全局范围,但我总是 运行 进入某些 threading/lockup我不完全理解的问题。

第一个问题是您不想从 suspendCoroutine 中启动新的协程,您需要重组代码以便不必执行此操作。这可以作为

private fun someMethod() = viewModelScope.launch {
    val client = billingClientDeferred.await()
    val finalResult = getPurchases(client)
}

在此之后只需更新 getPurchases 以将客户端作为参数排除并从中删除所有协程代码,只需调用客户端并 return 使用延续的结果。

您的代码的根本问题是您没有遵循 structured concurrency 的原则。

它本质上是一种并发管理模式,隐式地处理协程的范围和生命周期。这个想法是,当您使用不同的范围启动协程时,这些范围必须具有父子关系,这允许协程管理系统处理所有情况,例如

  1. 当内部协程抛出异常时会发生什么
  2. 当外部协程被取消或失败时会发生什么

恐怕您的代码并没有真正遵循这种模式并且存在以下问题

scope 与启动 getPurchases

CoroutineScope 没有任何关系

这是个大问题,取消outer scope会怎样,是取消inner coroutine还是inner coroutine会一直消耗资源?

您正在从挂起的函数中启动协程

正如 Roman Elizarov 解释的那样 here

Suspending functions, on the other hand, are designed to be non-blocking and should not have side-effects of launching any concurrent work. Suspending functions can and should wait for all their work to complete before returning to the caller.