Я хочу сделать что-то вроде следующего:
suspend fun getProducts(): List<Product> {
runBlocking {
launch {
val inAppProducts = withContext(Dispatchers.IO) {
queryProductDetails(...)
}
}
launch {
val subsProducts = withContext(Dispatchers.IO) {
queryProductDetails(...)
}
}
}
return inAppProducts + subsProducts
}
(Примечание: для краткости игнорирование нулевых проверок и аргументов функций)
Два вопроса:
val и использовать его вне блока кода? var единственный вариант?@dominicoder Плохо - обновил мой вопрос, чтобы включить launch.
Я собираюсь предположить, что причина для runBlocking и запуска двух сопрограмм заключается в том, что вы хотите, чтобы они выполнялись параллельно, а затем ждали обоих результатов. Если да, то для этого и нужен async.
Пример псевдокода, который может быть или не быть технически правильным, но вы иллюстрируете идею:
suspend fun getProducts(): List<Product> {
// Start first job and get a handle to it
val inAppProductsJob = async {
withContext(Dispatchers.IO) {
queryProductDetails(...)
}
}
// Start second job and get a handle to it
val subsProductsJob = async {
withContext(Dispatchers.IO) {
queryProductDetails(...)
}
}
// Waits for both jobs to complete, gets their results, and combines them
return inAppProducts.await() + subsProducts.await()
}
Вам нужно обернуть эту функцию либо в withContext, либо в coroutineScope, иначе у вас не будет доступа к приемнику CoroutineScope для вызова async.
Да, я просто подправил собственный код OP, чтобы проиллюстрировать идею, я не проверял его на правильность.
Я предполагаю, что они использовали runBlocking, чтобы иметь возможность запускать другие сопрограммы, поэтому отсутствие знаний о withContext и coroutineScope, которые дают вам область действия сопрограммы без блокировки, — половина их проблемы.
Вместо async { withContext(Dispatchers.IO) { ... } } почему бы и нет async(Dispatchers.IO) { ... }?
Никогда не используйте runBlocking в функции приостановки. Функция приостановки не должна блокироваться по соглашению. Вместо этого вы можете использовать coroutineScope или withContext, чтобы иметь возможность запускать параллельные сопрограммы внутри вашей функции приостановки.
launch для сопрограмм, которые ничего не производят. Вместо этого используйте async, если вы хотите что-то произвести. Это сразу запускает сопрограмму параллельно. Затем вызовите await() в Deferred сопрограммы, чтобы получить результат в функции приостановки.
suspend fun getProducts(): List<Product> = coroutineScope {
val inAppProducts = async {
queryProductDetails(...)
}
val subsProducts = async {
queryProductDetails(...)
}
return@withContext inAppProducts.await() + subsProducts.await()
}
Если queryProductDetails() — функция приостановки, то переключение на диспетчер ввода-вывода бессмысленно (если только мы не присоединяемся к огромным спискам), и мы можем заменить withContext на coroutineScope.
Спасибо, что поделился! Итак, queryProductDetails — это функция приостановки ссылка. Я не могу добавить async, если я не включу его внутрь withContext(Dispatchers.IO), то есть val inAppProducts = withContext(Dispatchers.IO) { async { queryProductDetails(...)}}. Это кажется правильным?
Обратите внимание на крайний withContext в моем коде — его не нужно использовать дважды. Но поскольку queryProductDetails — это функция приостановки, вы можете использовать вместо нее coroutineScope, чтобы сделать ее немного проще. withContext и coroutineScope в основном одинаковы, за исключением того, что withContext также позволяет вам изменять CoroutineContext.
Ах, я пропустил самый крайний withContext, спасибо! Теперь это работает. Но вы правы в другом комментарии, там много о withContext и coroutineScope, чего я не совсем понимаю. Есть ли у вас какие-либо рекомендуемые ресурсы, которые помогут лучше понять их?
Я не знаю ни одного ресурса, который четко объясняет все это в одном месте. :( Документация для функции coroutineScope довольно ясна и лаконична. Вы можете думать о withContext как об одном и том же плюс возможность изменять CoroutineContext. Чаще всего он используется для принудительного применения определенного Dispatcher. Вам нужно только диспетчер при вызове кода блокировки или при вызове функции, которая работает только в определенном потоке, например при работе с представлениями, которым требуется Main.
Вы можете просто удалить
runBlocking, что бесполезно.