#android #kotlin #kotlin-coroutines
#Android #kotlin #kotlin-сопрограммы
Вопрос:
В чем разница между GlobalScope и MainScope?
//Accessing data from Room
GlobalScope.launch {
v.tvStoreName.text = pfViewModel.getStoreName()
pageDetails.pageNumber = currentPage
pageDetails.pageSize = pageSize
pfViewModel.getTransactions(pageDetails, toolbarBuilder?.getDate()!!)
}
GlobalScope иногда выдает ошибку, которую очень трудно воспроизвести.
Фатальное исключение: android.view.ViewRootImpl $CalledFromWrongThreadException: только исходный поток, создавший иерархию представлений, может касаться своих представлений.
MainScope().launch {
var storeName = ""
withContext(Dispatchers.Default) {
storeName = pfViewModel.getStoreName()
}
v.tvStoreName.text = storeName
}
Ответ №1:
В чем разница между GlobalScope и MainScope?
MainScope
это CoroutineScope
приложение, которое Dispatchers.Main
по умолчанию использует диспетчер, который привязан к основному потоку пользовательского интерфейса.
Это объект, у GlobalScope
CoroutineScope
которого нет диспетчера в контексте сопрограммы. Это означает, что сопрограммы, запущенные в этой области, будут использовать Dispatchers.Default
диспетчер, который поддерживается пулом потоков (размер которого зависит от количества ядер вашего процессора).
GlobalScope
В Job
его контексте также нет, что означает, что структурированный параллелизм не применяется. Сопрограммы, запущенные в нем, никогда не отменяются автоматически, поэтому ими нужно управлять вручную. Вот почему обычно не рекомендуется использовать его, если у вас нет особых потребностей.
Только исходный поток, создавший иерархию представлений, может касаться своих представлений.
Эта ошибка возникает, когда вы пытаетесь изменить представления извне основного потока, что и происходит, если вы делаете это из сопрограмм, запущенных в GlobalScope
(потому что они поддерживаются отдельным пулом потоков).
В вашем втором фрагменте вы используете withContext(Dispatchers.Default)
, который запускает только эту часть кода в этом пуле потоков, а остальная часть выполняется в потоке пользовательского интерфейса. Вот почему обновление пользовательского интерфейса там в порядке.
Обратите внимание, что Room уже использует диспетчер с фоновым пулом потоков для своих запросов, поэтому вам не нужно переключать контекст вручную, как это, вы можете просто вызвать его из потока пользовательского интерфейса.
Боковое примечание: MainScope().launch { .. }
подобное использование — плохая идея, потому что оно страдает от той же проблемы с отменой GlobalScope
, что и . Чтобы использовать его правильно, вам нужно будет извлечь эту область в переменную / свойство, чтобы вы могли отменить ее при необходимости. Тем не менее, проще использовать существующую область. Android уже предоставляет готовую к использованию область сопрограммы в таких компонентах, как Activities, которые имеют жизненный цикл (см. lifecycle-runtime-ktx
Библиотека). Это называется lifecycleScope
. Вы должны запускать свои сопрограммы в этой области, чтобы они автоматически отменялись при уничтожении действия.
Комментарии:
1. Другими словами, мне лучше использовать lifecycleScope в этом правильном? По какой-то причине текущая реализация Room в этом проекте не позволяет получать доступ к данным в том виде, в каком они находятся в основном потоке.
2. Да, вы должны предпочесть
lifecycleScope
(илиviewModelScope
в зависимости от того, в каком компоненте вы находитесь)3. Последующий вопрос: что, если я не нахожусь ни в какой области, такой как
viewModelScope
norlifecycleScope
? Как сопрограмма реализуется в пользовательской библиотеке представлений?4. В общем, в этом случае лучше использовать функции приостановки или потоки. Но если вам действительно нужно запускать долговременные сопрограммы, вы можете создать пользовательскую область, которую вы отменяете соответствующим образом, используя любой жизненный цикл вашего пользовательского класса.