Будут ли сопрограммы обходить поток?

#kotlin #kotlin-coroutines

Вопрос:

Я использую сопрограммы для сетевого вызова и задавался вопросом, есть ли лучший способ для сетевого вызова с приостановкой.

Это требование заключается в:

  1. Вызовите сетевой API
  2. остановите выполнение до получения ответа
  3. действовать в соответствии с ответом API

Активность:

 private fun prepareUnlockUrl(url: String) {
        CoroutineScope(Dispatchers.IO).launch {
            val result = async { mViewModel.requestAccess(url) }

            when (result.await()) {
                Log.i(TAG, "do something with 'result'")
            }
        }
    }

 

и модель представления состоит:

 suspend fun requestAccess(url: String): Response {
      return suspendCoroutine { continuation ->
            // Do network call and get response
            continuation.resume(response)
      }
}

 

Комментарии:

1. Зависит от того, какой сетевой API вы используете. Возможно, он был встроен в поддержку сопрограмм.

2. Это внутренний API, использующий шаблон репозитория retrofit

Ответ №1:

Есть множество вещей, которые неправильны в prepareUnlockUrl :

  1. Вы не должны создавать область сопрограммы на лету, как это. Область действия должна быть привязана к какому-то жизненному циклу и соответствующим образом отменена.
    • если вам просто нужно запустить что-то в определенном контексте сопрограммы, вы можете использовать withContext(Dispatchers.IO) вместо этого. Обратите внимание, что это функция приостановки, поэтому вы просто меняете контекст, а не создаете здесь сопрограмму.
    • если вы действительно хотите создать сопрограмму (если вы не можете создать свою функцию suspend ), у вас уже должна быть готовая к использованию область сопрограммы, доступная в действиях, таких как lifecycleScope (ознакомьтесь с этим документом о сопрограммах в Android)
  2. звонить async , а потом await() сразу же никогда не бывает полезно.
    • Если вы не измените контекст сопрограммы с помощью async вызова (что в данном случае относится к вам), вы можете просто вызвать функцию suspend напрямую без переноса, async и это будет иметь тот же эффект
    • Если бы вы использовали пользовательский контекст сопрограммы в async вызове (которого здесь нет), вы могли бы просто использовать withContext(someContext) { suspendCall } вместо этого async(someContext) { suspendCall() }.await()

Так что, в конце концов, я думаю, вам следует либо написать:

 private suspend fun prepareUnlockUrl(url: String) = withContext(Dispatchers.IO) {
    mViewModel.requestAccess(url)
    Log.i(TAG, "do something with 'result'")
}
 

Или, если вы suspend по какой-либо причине не можете создать свою функцию, запустите сопрограмму в существующей lifecycleScope :

 private fun prepareUnlockUrl(url: String) = lifecycleScope.launch(Dispatchers.IO) {
    mViewModel.requestAccess(url)
    Log.i(TAG, "do something with 'result'")
}
 

Теперь о requestAccess том , что я действительно не могу сказать, если вы не должны, как именно выполняется сетевой вызов, но, к счастью, это не зависит от других проблем.

Комментарии:

1. Спасибо, причина выбора сопрограмм заключалась в том, чтобы дождаться результата от requestAccess() . Я предпочитаю вторую форму, упомянутую выше, если она будет служить этой цели.

2. @Faisal, использующий функции приостановки, уже использует сопрограммы, разница в том, на каком уровне мы запускаем сопрограмму для выполнения работы. Обычно идея состоит в том, чтобы как можно больше приостанавливать функции, а затем запускать сопрограммы в компонентах верхнего уровня, которые имеют жизненный цикл и, следовательно, могут определять соответствующие области сопрограмм (например, компоненты, у которых есть lifecycleScope или viewModelScope ).

3. Есть ли какие-либо ограничения в доступе к общим предпочтениям из suspend метода или suspendCoroutine блока?

4. @Фейсал, насколько мне известно, никаких ограничений, но я давно не занимался разработкой Android 😀 Это должно быть нормально, потому что вы всегда можете вызвать любую функцию из функций приостановки (обратное неверно). То есть, если нет особых ограничений по потоку, в этом случае вы должны убедиться, что используете правильный диспетчер withContext (например, не используйте commit() в своем основном потоке, используйте apply() вместо этого).