Flutter / Dart: исключение одновременного изменения без изменения элементов списка

#list #flutter #dart

#Список #flutter #dart

Вопрос:

Во время тестирования и отладки приложения я заметил, что было исключение, которое в основном происходит только во время отладочного тестирования, внутри цикла for, который перебирает список:

[ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: Concurrent modification during iteration: Instance(length:0) of '_GrowableList'.

Я искал и обнаружил, что в основном это происходит, если вы меняете сам список во время итерации, но я не вижу, где это происходит в коде:

Основная функция:

 static Future<void> save(EntryModel entry) async {
    ...
      List<TagModel> tagsList = entry.tags;
      List<int> tagIdsInserted = [];
      if (tagsList != null amp;amp; tagsList.isNotEmpty) {
        for (TagModel tag in tagsList) {

          //Error happens inside this loop
          int tagIdInserted = await TagContract.save(tag); //this function does not alter the tag in any way.

          if (tagIdInserted == null || tagIdInserted <= 0) {
            throw Exception('Invalid TagID!');
          }
          tagIdsInserted.add(tagIdInserted);
        }
      }
  

Что происходит, так это то, что во время первой итерации она работает нормально, но вторая или третья List<TagModel> tagsList внезапно становится пустой, в том числе из исходного объекта ( entry переданного функции).

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

Заранее спасибо.

Комментарии:

1. Содержит ли TagModel ссылку на свою EntryModel ? Что делает TagContract при сохранении? Существует ли какой-либо менеджер моделей, который может очищать теги EntryModel? Недостаточно информации для устранения проблемы. Вам придется использовать отладчик для пошагового выполнения кода.

2. @hola Нет, TagModel не содержит никаких ссылок на запись. TagContract записывает информацию о TagModel в базе данных SQLite. Я попытался отладить код и заметил, что на второй или третьей итерации цикла EntryModel по какой-то причине очищает свою ссылку на список TagModel. У меня до сих пор нет идеи, что может быть причиной проблемы, но ответ First_Strike, похоже, проясняет некоторые проблемы.

Ответ №1:

Старайтесь избегать использования await внутри цикла, это слишком опасно.

Вы должны понимать, как выполняется асинхронный код. Если await обнаружен и Future не может вернуться синхронно, среда выполнения приостановит выполнение этой функции и перейдет к любым другим заданиям, которые находятся в верхней части очереди.

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

Попытайтесь понять следующий пример. Это напрямую вызовет исключение.

 void main() {
  List<int> ids = [1,2,3];
  test(ids);
  ids.add(1); // If the async function get suspended, this becomes the top of the queue.
}

void test(List<int> ids) async {
  for (final id in ids) {
    await Future.delayed(Duration(milliseconds: 10));
  }
}
  

В асинхронном программировании избегайте написания await who зависит от открытых общих состояний.

Для списка асинхронных задач всегда подготавливайте их в an Iterable<Future> , затем используйте Future.wait для их синхронизации и получения результата в одном await .

Для вашего кода

 final results = await Future.wait(tagsList.map((tag)=>TagContract.save(tag)))
  

Комментарии:

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

2. Dart Future работает так же, как и Javascript Promise . Вы можете обратиться к обеим документациям. Лучше подготовить и использовать локальное состояние перед an await , потому await что означает «здесь планировщик времени выполнения может выполнять произвольные другие задачи, чтобы ждать»