#kotlin #kotlin-coroutines
Вопрос:
Я использую сопрограммы для сетевого вызова и задавался вопросом, есть ли лучший способ для сетевого вызова с приостановкой.
Это требование заключается в:
- Вызовите сетевой API
- остановите выполнение до получения ответа
- действовать в соответствии с ответом 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
:
- Вы не должны создавать область сопрограммы на лету, как это. Область действия должна быть привязана к какому-то жизненному циклу и соответствующим образом отменена.
- если вам просто нужно запустить что-то в определенном контексте сопрограммы, вы можете использовать
withContext(Dispatchers.IO)
вместо этого. Обратите внимание, что это функция приостановки, поэтому вы просто меняете контекст, а не создаете здесь сопрограмму. - если вы действительно хотите создать сопрограмму (если вы не можете создать свою функцию
suspend
), у вас уже должна быть готовая к использованию область сопрограммы, доступная в действиях, таких какlifecycleScope
(ознакомьтесь с этим документом о сопрограммах в Android)
- если вам просто нужно запустить что-то в определенном контексте сопрограммы, вы можете использовать
- звонить
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()
вместо этого).