【问题标题】:CoroutineScope - CompletableDeferred cancellationCoroutineScope - CompletableDeferred 取消
【发布时间】:2018-10-12 07:37:43
【问题描述】:

我有两个关于这个话题的问题。我将在 android 中使用这些用例类,并尝试实现类似于 https://www.youtube.com/watch?v=Sy6ZdgqrQp0 的架构,但我需要一些答案。

1) 我有一个延迟的异步构建器,当我取消作业时, 其他连锁店也取消了。此代码打印“呼叫已取消”。但我不确定我做的是否正确。

fun main(args: Array<String>) = runBlocking<Unit> {
    val job = GlobalScope.launch {
        println(getUser())
    }
    job.cancelAndJoin()
}

suspend fun getUser() = getUserDeferred().await()


suspend fun getUserDeferred() = coroutineScope {

    val request = Request.Builder()
            .url("https://jsonplaceholder.typicode.com/users")
            .build()

    val call = OkHttpClient().newCall(request)

    val deferred = async(Dispatchers.IO) {
        val body = call.execute()
        body.body()?.string() ?: ""
    }

    deferred.invokeOnCompletion {
        if (deferred.isCancelled) {
            println("Call cancelled")
            call.cancel()
        }
    }
    deferred
}

2) 我找不到取消这个的方法。我想在retrofit2调用适配器中使用这个,有没有更好的方法来处理这种情况。

fun main(args: Array<String>) = runBlocking<Unit> {
    val job = GlobalScope.launch {
        println(getUser1())
    }
    job.cancelAndJoin()
}

suspend fun getUser1() = getUser1Deferred().await()


fun getUser1Deferred(): Deferred<String> {
    val request = Request.Builder()
            .url("https://jsonplaceholder.typicode.com/users")
            .build()

    val call = OkHttpClient().newCall(request)

    val deferred = CompletableDeferred<String>()

    call.enqueue(object : Callback {

        override fun onFailure(call: Call, e: IOException) {
            deferred.complete("Error")
        }

        override fun onResponse(call: Call, response: Response) {
            deferred.complete(response.body()?.string() ?: "Error")
        }

    })

    deferred.invokeOnCompletion {
        if (deferred.isCancelled) {
            println("Call cancelled")
            call.cancel()
        }
    }
    return deferred
}

【问题讨论】:

    标签: android kotlin kotlin-coroutines


    【解决方案1】:

    您应该避免使用第一种方法,因为它会阻塞线程池中的线程。使用第二种方法,您可以双向传播取消。如果您取消Deferred,它将取消调用,如果调用失败,它将取消Deferred,但出现异常。

    fun getUserAsync(): Deferred<String> {
        val call = OkHttpClient().newCall(Request.Builder()
                .url("https://jsonplaceholder.typicode.com/users")
                .build())
        val deferred = CompletableDeferred<String>().apply {
            invokeOnCompletion {
                if (isCancelled) {
                    call.cancel()
                }
            }
        }
        call.enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                deferred.complete(response.body()?.string() ?: "Error")
            }
            override fun onFailure(call: Call, e: IOException) {
                deferred.cancel(e)
            }
    
        })
        return deferred
    }
    

    但是,走Deferred 路线可能是一个红鲱鱼。如果你取消它,根本原因是你正在放弃你正在做的整个任务。你应该取消它运行的整个协程。如果你正确实现structured concurrency,如果你的活动被破坏,一切都会自动发生。

    所以我的建议是使用这个代码:

    suspend fun getUser() = suspendCancellableCoroutine<String> { cont ->
        val call = OkHttpClient().newCall(Request.Builder()
                .url("https://jsonplaceholder.typicode.com/users")
                .build())
        cont.invokeOnCancellation {
            call.cancel()
        }
        call.enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                cont.resume(response.body()?.string() ?: "Error")
            }
            override fun onFailure(call: Call, e: IOException) {
                cont.resumeWithException(e)
            }
    
        })
    }
    

    如果您绝对需要Deferred,因为您在后台同时运行它,使用上面的方法很容易:

    val userDeferred = this.async { getUser() }
    

    我假设this 是您的活动,这也是CoroutineScope

    【讨论】:

    • 好吧,我再次尝试延迟,这次成功了。我接受答案。但有一件事仍然让我感到困惑。是的,我可以将异步用于延迟,我将保留它的参考以供将来取消。我只是想知道有没有办法将此引用与自动取消的协程范围结合起来?。
    • 这和launch 一样。所以你只需要确保你使用async的活动协程范围。
    • 我询问了 CompletableDeferred 示例,我再次尝试了,在 Deferred.invokeOnCompletion 块中 Deferred.isCancelled 为假,Deferred.isActive 为假,因此我可以使用 isActive 参数取消调用适配器中的调用。再次感谢。
    【解决方案2】:

    第二种情况没有取消的原因是因为您使用的是CompletableDeferred。它不是作为协程启动的,因此不是您父协程的子进程。因此,如果您取消父级,它不会取消延迟。

    它在第一种情况下有效,因为async 启动了一个新的子协程,该协程链接到父协程。当您取消其中任何一个时,它们都会被取消。

    为了将Deferred 链接到您的父作业,您需要对其进行引用并使用invokeOnCompletion

    var deferred : Deferred<Void>? = null
    launch {        
       deferred = retroService.someDeferredCall()
       deferred.await()
    }.invokeOnCompletion {
       //job was cancelled.  Probably activity closing.
       if(it is CancellationException) {
          deferred?.let { it.cancel() }
       }
    }
    

    不是很漂亮,但应该可以完成工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-06-13
      • 1970-01-01
      • 2021-10-18
      • 2020-01-21
      • 1970-01-01
      • 2020-04-09
      • 1970-01-01
      • 2018-08-04
      相关资源
      最近更新 更多