CoroutineScope - CompletableDeferred отмена - PullRequest
0 голосов
/ 12 октября 2018

У меня есть два вопроса на эту тему.Я буду использовать их в 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 голосов
/ 27 марта 2019

Причина, по которой второй случай не отменяется, заключается в том, что вы используете 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() }
   }
}

Не очень красиво, но должно выполнить работу.

0 голосов
/ 12 октября 2018

Следует избегать первого подхода, поскольку он блокирует поток в пуле потоков.Используя второй подход, вы можете распространять отмену в обоих направлениях.Если вы отмените 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наверное красная сельдь.Если вы отменяете его, основная причина в том, что вы спасаетесь от всей задачи, которую делаете.Вместо этого вы должны отменить всю сопрограмму, в которой она выполняется. Если вы правильно реализуете структурированный параллелизм , все будет происходить автоматически, если ваша активность будет разрушена.

Поэтому я рекомендую использовать этот код:

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.

...