Почему андроид комната вставляет объект в конце асинктической задачи, генерируя его (объект)? - PullRequest
2 голосов
/ 07 июля 2019

Сводка

Комната немедленно вставляет сущности, созданные с помощью пользовательского интерфейса, но задерживает те, которые были отправлены асинхронной задачей, до (дальнего) конца генерирующей асинктаски: полученные объекты сущности можно использовать и отображать в пользовательском интерфейсе, но без каких-либоидентификатор из базы данных, препятствуя любой другой операции, полагаясь на идентификатор. Операция вставки происходит только тогда, когда генерация асинхронной задачи правильно остановлена. Почему?И как решить эту проблему?

Больше контекста

Генерирование asynctask

Мы используем asynctask для мониторинга сокета и отправки назад некоторых событий (как объекта Room)в хранилище приложений (как предусмотрено компонентами архитектуры Android).Эта асинхронная задача в основном работает непрерывно в фоновом режиме (с некоторым установленным регулярным сном) и останавливается только за некоторое время до окончания использования приложения (если все сделано правильно).До сих пор это не вызывало у нас никаких проблем с тем, чтобы так сильно отклоняться от первоначальной концепции краткосрочной асинхронной задачи.Я в значительной степени знаю, что мы могли бы улучшить дизайн, но это еще одна тема / вопрос / временная дыра; -).

Операция вставки комнаты

Вставка происходит через выделенную асинхронную задачу, гдеВозвращенный идентификатор записи в базе данных возвращается к только что вставленной сущности (см. код ниже).Это регистрируется, и объекты из пользовательского интерфейса сохраняются «немедленно», они получают свой идентификатор, и все в порядке.Сгенерированные асинктической сущностью объекты хорошо ждут остановки своей «родительской» задачи, а затем все вставляются.

Состав сущности

Сначала сущность была сгенерирована внутри асинхронной задачи и отправлена ​​черезсообщение о прогрессе.Затем конструкция объекта была перемещена за пределы асинхронной задачи и на том же уровне построения событий пользовательского интерфейса, но с тем же поведением.Этими событиями являются longs (временные метки) и несколько строк.

С порождающей асинхронной задачи все начинается отсюда:

    @Override
    protected void onProgressUpdate(OnProgressObject... values) {
        OnProgressObject onProgressObject = values[0];

        if (onProgressObject instanceof OnProgressEvent) {
            eventRecipient.sendAutoEvent(((OnProgressEvent) onProgressObject).autoEvent);
        }
    }

EventRecipient - это репозиторий Events:

    public void sendAutoEvent(AutoEvent autoEvent) {
        Log.d(LOG_TAG, "got an autoevent to treat...");
        EventModel newEvent = EventModel.fromCub(
                autoEvent.cubTimeStamp,
                autoEvent.description,
                autoEvent.eventType
        );
        addEvent(newEvent);
    }


    public void addEvent(EventModel event) {

        new insertEventAsyncTask(event).execute(event);

        // other operations using flawlessly the "event"...
    }

    private class insertEventAsyncTask extends AsyncTask<EventModel, Void, Long> {
        private EventModel eventModel;
        public insertEventAsyncTask(EventModel eventModel) {
            this.eventModel = eventModel;
        }
        @Override
        protected Long doInBackground(EventModel... eventModels) {
            // inserting the event "only"
            return eventDao.insert(eventModels[0]);
        }

        @Override
        protected void onPostExecute(Long eventId) {
            super.onPostExecute(eventId);
            // inserting all the medias associated to this event
            // only one media is expected this way though.
            eventModel.id = eventId;
            Log.d(LOG_TAG, "event inserted in DB, got id : " + eventId);
        }

    }

Ответы [ 2 ]

1 голос
/ 07 июля 2019

Я в значительной степени знаю, что мы могли бы улучшить дизайн, но это еще одна тема / вопрос / временная дыра

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

Моя интерпретация вашей проблемы такова: у вас есть внешний AsyncTask (тот, у которого метод onPublishProgress() показан в первом листинге кода).Вы выполняете это с execute().Внутри этого внешнего AsyncTask у вас есть внутренний AsyncTask (тот, что из вашего хранилища).Вы выполняете это с execute().И ваша жалоба состоит в том, что внутренний AsyncTask не запускается, пока не завершится внешний AsyncTask.

Если это так, ваша проблема в том, что execute() является однопоточным, и вы связываете этот потокAsyncTask работает бесконечно.Пока ваш внешний AsyncTask не завершит свою фоновую работу и не вернется из doInBackground(), внутренний AsyncTask блокируется.

"Можем ли мы продолжать использовать хаки?"Решение состоит в том, чтобы продолжать использовать AsyncTask, но переключиться на executeOnExecutor() вместо execute(), предоставляя пул потоков для использования.AsyncTask.THREAD_POOL_EXECUTOR будет кандидатом.

"Хорошо, мы можем немного это почистить?"решение состоит в том, чтобы заменить оба экземпляра AsyncTask простыми объектами Thread или непосредственным использованием какого-либо многопоточного пула потоков (см. Executors).AsyncTask устарел, но в той степени, в которой он полезен, используйте его только тогда, когда вам нужно выполнить работу с основным потоком приложения (onPostExecute()) после завершения фоновой работы (doInBackground()) ,Ни одна из ваших AsyncTask реализаций не должна выполнять работу с основным потоком приложения после завершения фоновой работы, поэтому вам не нужно AsyncTask для любой из них.Так, например, ваш поток run-forever может быть Thread, в то время как вы используете пул потоков внутри вашего репозитория для вызовов DAO.

(«эй, можем ли мы стать современными в наших потоках», чтобы согласиться с нашим использованием компонентов архитектуры? »решение состоит в том, чтобы переключиться на сопрограммы RxJava или Kotlin в сочетании с LiveData - это гораздо больше работы, но у каждого из них есть свои преимущества по сравнению с ручным управлением потоками)

0 голосов
/ 14 июля 2019

Почему?

По сути, это было написано в документации AsyncTask : все асинхронные задачи выполняются последовательно в уникальном фоновом потоке .

Мой код, даже без вложенной асинхронной задачи, блокировал этот поток практически бесконечной задачей, задерживая все операции с базой данных до ее завершения (или сбоя приложения, что привело к некоторой потере данных).

Быстрое решение: перемещение AsyncTask в поток

Другие альтернативы были приятно перечислены (CommonsWare) [https://stackoverflow.com/a/56925864/9138818], Вот шаги, которые я предпринял, чтобы решить эту проблему.

Основная трудность заключалась в том, чтобы перенаправить код, который выполнялся в потоке пользовательского интерфейса (onPreExecute, onProgressUpdate, onPostExecute), через обработчик , связанный с основным потоком.

Первым шагом было получение ссылки на обработчик:

// Inside Runnable task's constructor :
// get the handler of the main thread (UI), needed for sending back data.
this.uiHandler = new Handler(Looper.getMainLooper());

Затем рефакторинг doInBackground соответствует сигнатуре основного метода Runnable:

// previously "public String doInBackground()"
//  returned value handled through publishProgress.
@Override
public void run() {
    // recommended by Android Thread documentation
    android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

    // code previously in doInBackground

Теперь код в onProgressUpdate (который был вызван publishProgress внутри метода doInBackground) был перемещен в Runnable, размещенный в обработчике потока пользовательского интерфейса:

// asynctask method onProgressUpdate was renamed publishProgress => 
//  doInBackground's body is almost untouched.
private void publishProgress(final OnProgressObject... values) {
    uiHandler.post(new Runnable() {
        @Override
        public void run() {
            // move here code previously in the AsyncTask's publishProgress()
        }
    });

}

Наконец, мне пришлось изменить способ создания, запуска и остановки задачи, используя Thread.interrupted вместо isCancelled и создав задачу Runnable перед потоком:

public void startCUBMonitoring() {
    if (autoEventThread == null) {
        Log.d(LOG_TAG, "startCUBMonitoring");
        addUIEvent("CUB MONITORING STARTED", "CUB_connexion");
        SessionRepository sessionRepository =
                ElabsheetApplication.getInstance().getSessionRepository();

        // Creation of the task
        AutoEventTask autoEventTask = new AutoEventTask(
                this,
                sessionRepository,
                sessionRepository.getCUBConfig()
        );
        autoEventThread = new Thread(autoEventTask);
        autoEventThread.start();
    }

}

public void stopCUBMonitoring() {
    if (autoEventThread != null) {
        Log.d(LOG_TAG, "stopCUBMonitoring");
        addUIEvent("CUB MONITORING STOPPED", "CUB_connexion");
        autoEventThread.interrupt();
        autoEventThread = null;
    }
}

Надеялся, что это может помочь ...

...