CoroutineScope-CompletableDeferred取消

时间:2018-10-12 07:37:44

标签: android kotlin kotlin-coroutines

我对此主题有两个问题。我将在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
}

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:0)

第二种情况未取消的原因是因为您正在使用CompletableDeferred。它不是作为协程启动的,因此也不是您的父协程的孩子。因此,如果取消父项,则不会取消延期。

它在第一种情况下有效,因为async启动了一个新的子协程,该协程与父级关联。当您取消其中一个时,它们都将被取消。

要将Deferred链接到您的父级Job,您需要对其进行引用并使用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() }
   }
}

不太漂亮,但应该完成工作。