Меня особенно беспокоит вставка данных, инициированных пользователем, в локальную базу данных.
Следующий шаблон преобладает в примерах (в том числе из официальных источников, например, JetBrains, Google / Android) для использования сопрограмм Kotlin в сочетании с моделями представления [Компоненты архитектуры Android].
class CoroutineScopedViewModel : ViewModel(), CoroutineScope {
private val _job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + _job
override fun onCleared() {
super.onCleared()
_job.cancel()
}
fun thisIsCalledFromTheUI() = launch {
/* do some UI stuff on the main thread */
withContext(Dispatchers.IO) {
try {
/* do some IO, e.g. inserting into DB */
} catch (error: IOException) {
/* do some exception handling */
}
}
}
}
Это мое понимание документации, что в приведенном выше примере сопрограммы, запущенные в контексте пользовательского интерфейса (определенном через coroutineContext
), будут отменены, когда ViewModel
будет уничтожен, но что код в блоке withContext(Dispatchers.IO)
доберутся до завершения.
Но, прежде чем я перейду к рефакторингу своего проекта из (до 1.0.0) модели сопрограмм с глобальным охватом (запуск / асинхронность), я чувствую, что мне нужно просто прояснить некоторые вещи:
Правильно ли я прочитал документацию? Или уничтожение модели просмотра до того, как блок withContext(Dispatchers.IO)
завершится, вызовет отмену и этого задания? Т.е. Можно ли использовать эту модель для вставки данных в мою БД, или может возникнуть какая-то странная проблема с синхронизацией, когда пользователь отвечает или иным образом заставляет владельца ViewModel закрыться, что в конечном итоге приводит к потере данных?
Я не хочу непреднамеренно вводить ошибку синхронизации, потому что я что-то неправильно понял и поэтому преобразовал свой код в модель, аналогичную показанной выше.
РЕДАКТИРОВАТЬ:
Итак, я решил провести небольшой тест, и мне кажется, что все эти примеры, использующие эту модель для записи в базу данных, могут содержать фундаментальную ошибку.
Модификация кода для регистрации происходящего, как такового:
class ChildViewModel : ViewModel(), CoroutineScope {
private val _job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + _job
override fun onCleared() {
super.onCleared()
Log.d("onCleared", "Start")
_job.cancel()
Log.d("onCleared", "End")
}
fun thisIsCalledFromTheUI() = launch {
Log.d("thisIsCalledFromTheUI", "Start")
GlobalScope.launch(Dispatchers.IO) {
Log.d("GlobalScope", "Start")
delay(15000)
Log.d("GlobalScope", "End")
}
withContext(Dispatchers.IO) {
Log.d("withContext", "Start")
delay(10000)
Log.d("withContext", "End")
}
Log.d("thisIsCalledFromTheUI", "End")
}
}
В результате, если вы дадите ему поработать до конца:
D/thisIsCalledFromTheUI: Start
D/GlobalScope: Start
D/withContext: Start
D/withContext: End
D/thisIsCalledFromTheUI: End
D/GlobalScope: End
Но если вы закроете Fragment / Activity (не приложение) до завершения withContext
, вы получите следующее:
D/thisIsCalledFromTheUI: Start
D/GlobalScope: Start
D/withContext: Start
D/GlobalScope: End
Это указывает, по крайней мере, для меня, что вы не можете использовать это для записи непереходных данных в БД.