Подходит ли модель Kotlin Structured Concurrency [сопрограмма] с пользовательским интерфейсом для записи в БД? - PullRequest
0 голосов
/ 16 ноября 2018

Меня особенно беспокоит вставка пользовательских данных в локальную базу данных.

В примерах преобладает следующая схема (в том числе из официальных источников, например JetBrains, Google / Android)для использования сопрограмм Kotlin в сочетании с [компонентами архитектуры Android] ViewModels.

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) будет запущен до конца.

Но прежде чем я начну рефакторинг своего проекта из ((pre-1.0.0) модель сопрограммы в глобальном масштабе (launch / async), я чувствую, что мне нужно просто уточнить некоторые вещи:

Правильно ли я прочитал документацию?Или уничтожение модели представления перед выполнением блока 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

Что указывает, по крайней мере мне, на то, что вы не можете использовать это для записи нестационарных данных в БД.

1 Ответ

0 голосов
/ 16 ноября 2018

В моем понимании документации, в приведенном выше примере сопрограммы, запущенные в контексте пользовательского интерфейса (определенные через coroutineContext), будут отменены при уничтожении ViewModel, но код в withContext(Dispatchers.IO) блок будет запущен до конца.

Это неверное прочтение документации.withContext не запускает другую сопрограмму, она просто меняет контекст текущей сопрограммы на время ее блока.Следовательно, эта сопрограмма будет отменена, как и все остальные сопрограммы, которые вы запускаете, без предоставления нового родительского контекста, с которым связано другое задание (или вообще без задания, например GlobalScope).

Однакопредлагаемая вами идея использовать GlobalScope для постоянных операций - это просто локальное исправление для сценария, который вы тестируете, вы все еще не получаете гарантии, что он будет работать до конца.Пользователь может полностью выйти из приложения, а Android - завершить процесс.

Поэтому, если ваша цель - создать действительно надежное приложение, вы должны учитывать тот факт, что до завершения сопрограммы информация не была записана вDB.Надеемся, что вы выполняете операцию внутри транзакции БД, которая автоматически откатится, если ваша программа будет убита, иначе будет невозможно предотвратить несоответствия.

...