Flutter: избегайте зависания пользовательского интерфейса, когда выполняется массивная операция с базой данных.

ОБНОВЛЕНИЕ (15 июля 2020 г.)

Ответ mFeinstein на данный момент является единственным ответом, который дает мне первое приемлемое решение.


ВОПРОС

Я должен спросить вас, как лучше всего делать то, что я пытаюсь сделать:

  1. Вызов веб-сервиса в асинхронном режиме
  2. Разбор ответа
  3. Выполнение массовых операций с базой данных

Все это без зависания анимации прогресса, например, неопределенного индикатора выполнения.

С первым и вторым пунктом проблем нет. Проблема возникает на третьем этапе, когда происходит массивная вставка базы данных. И я еще не понимаю, как правильно реализовать этот материал.

Какой-то псевдокод для уточнения

Интерфейс (отображается диалоговое окно и отображается индикатор выполнения...)

void callWS() async {
    MyProgressDialog _dialog = DialogHelper.showMyProgressDialog(_context, "Data", "Loading...");
    await getDataFromService();
    _dialog.close();
  }

СОЕДИНЕНИЕ (индикатор выполнения не зависает)

   static Future<void> getDataFromService() async {
    String uri = MY_URI;
    String wsMethod = MY_WS_METHOD;
    String wsContract = MY_WS_CONTRACT;

    SoapObject myRequest = SoapObject.fromSoapObject(namespace: my_namespace, name: wsMethod);

    MyConnectionResult response = await _openMyConnection(myRequest, uri, wsContract, wsMethod);
    if (response.result == MyResultEnum.OK) {
      await _parseResponse(response.data);
    }
  }

БАЗА ДАННЫХ (индикатор выполнения зависает)

  static Future<void> _parseResponse(xml.XmlElement elements) async {
    Database db = await MyDatabaseHelper.openConnection();
    db.transaction((tx) async {
      Batch batch = tx.batch();
      for (xml.XmlElement oi in elements.children) {
        int id = int.parse(oi.findElements("ID").first.text);
        String name = oi.findElements("NAME").first.text;

        DatabaseHelper.insertElement(
          tx,
          id: id,
          name: name,
        );
      }
      batch.commit(noResult: true);
    });
  }

НЕ РАБОТАЕТ АЛЬТЕРНАТИВА

Я тоже видел подход к функции вычисления, но кажется, что есть проблема в sqflite package, когда я вызываю операцию БД. Например:

  static Future<void> performDelete() async {
    Database db = await openMyConnection();
    compute(_performDeleteCompute, db);
  }

  static void _performDeleteCompute(Database db) async {
    db.rawQuery("DELETE MYTABLE");
  }

Console error:'
-> Unhandled Exception: Exception: ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized. 
-> If you are running an application and need to access the binary messenger before runApp() has been called (for example, during plugin initialization),
then you need to explicitly call the WidgetsFlutterBinding.ensureInitialized() first.
-> error defaultBinaryMessenger.<anonymous closure> (package:flutter/src/services/binary_messenger.dart:76:7)
    #1      defaultBinaryMessenger (package:flutter/src/services/binary_messenger.dart:89:4)
    #2      MethodChannel.binaryMessenger (package:flutter/src/services/platform_channel.dart:140:62)
    #3      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:146:35)
    #4      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:329:12)
    #5      invokeMethod (package:sqflite/src/sqflite_impl.dart:17:13)
    #6      SqfliteDatabaseFactoryImpl.invokeMethod (package:sqflite/src/factory_impl.dart:31:7)
    #7      SqfliteDatabaseMixin.invokeMethod (package:sqflite_common/src/database_mixin.dart:287:15)
    #8      SqfliteDatabaseMixin.safeInvokeMethod.<anonymous closure> (package:sqflite_common/src/database_mixin.dart:208:43)
    #9      wrapDatabaseException (package:sqflite/src/exception_impl.dart:7:32)
    #10     SqfliteDatabaseFactoryImpl.wrapDatabaseException (package:sqflite/src/factory_impl.dart:27:7)
    #11     SqfliteDatabaseMixin.safeInvokeMethod (package:sqflite_common/src/database_mixin.dart:208:15)
    #12     SqfliteDatabaseMixin.txnRawQuery.<anonymous closure> (package:sqflite_common/src/database_mixin.dart:394:36)
    #13     SqfliteDatabaseMixin.txnSynchronized.<anonymous closure> (package:sqflite_common/src/database_mixin.dart:327:22)
    #14     BasicLock.synchronized (package:synchronized/src/basic_lock.dart:32:26)
    #15     SqfliteDatabaseMixin.txnSynchronized (package:sqflite_common/src/database_mixin.dart:323:33)
    #16     SqfliteDatabaseMixin.txnRawQuery (package:sqflite_common/src/database_mixin.dart:393:12)
    #17     SqfliteDatabaseExecutorMixin._rawQuery (package:sqflite_common/src/database_mixin.dart:126:15)
    #18     SqfliteDatabaseExecutorMixin.rawQuery (package:sqflite_common/src/database_mixin.dart:120:12)
    #19     DatabaseHelper._performDeleteCompute(package:flutter_infocad/Database/DatabaseHelper.dart:368:8)'

А также при явном вызове WidgetsFlutterBinding.ensureInitialized() как первого в runApp(), как это предлагается в журнале ошибок, ничего не происходит.


person kinghomer    schedule 29.05.2020    source источник
comment
Вы пытались связать вызовы без ожидания? как db.openConnection().then() Пожалуйста, попробуйте весь путь от вызова WS   -  person Vilsad P P    schedule 29.05.2020
comment
Я попробую, но затем реализует обратный вызов, ожидание - это своего рода ожидание конца, а затем продолжайте. В первом случае прогресс-бар быстро доходит до конца, не ждет. Во втором случае все операции выполняются последовательно, ожидая для каждой возвращаемое значение. Представьте, что у вас есть 10 операций. Начните с отображения индикатора выполнения. Тогда ждите 10 выводов. Завершите скрытие индикатора выполнения. Во втором случае вместо этого вы должны показать индикатор выполнения, запустить 10 операций через обратный вызов, параллельно. Отслеживайте каждый результат обратного вызова, а когда все операции завершатся, скройте индикатор выполнения.   -  person kinghomer    schedule 29.05.2020
comment
Однако я думаю, что проблема заключается в массивной операции, которая выполняется в основном потоке. Вот почему я безуспешно пытался использовать функцию вычисления.   -  person kinghomer    schedule 29.05.2020
comment
пожалуйста, проверьте этот документ medium.com/flutter-community/ он говорит, что await может заблокировать основной поток. лучше реализовать обратный вызов   -  person Vilsad P P    schedule 29.05.2020
comment
также это может помочь blog.usejournal. ком/   -  person Vilsad P P    schedule 29.05.2020
comment
Я попытался вызвать 2 функции записи базы данных (со 100 операциями INSERT для каждой) с обратным вызовом (с помощью .then((_){})). Существует проблема. Обратный вызов подразумевает параллельный запуск 2-х функций. Таким образом, в БД выполняются одновременные записи, которые подразумевают тяжелые операции на ЦП, поэтому пользовательский интерфейс в любом случае зависает.   -  person kinghomer    schedule 29.05.2020
comment
async - это параллелизм в том же потоке, во второй статье говорится :(. Я думаю, что сейчас единственный хороший способ - это изолировать. Но пакет sqflite не работает, когда вы перемещаете операцию БД внутри Изолировать! Черт!   -  person kinghomer    schedule 29.05.2020


Ответы (3)


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

Решение состоит в том, чтобы быть умным в том, как использовать этот единственный поток.

Dart будет иметь очередь событий с кучей Futures, ожидающих обработки. Как только движок Dart увидит await, он позволит другому Future захватить Single Thread и запустить его. Таким образом, один Future будет запускаться внутри Isolate.

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

Эквивалент для вашего кода будет примерно таким (при условии, что for - это то, что требует много времени для выполнения из-за большой коллекции, а не отдельных шагов):

static Future<void> _parseResponse(xml.XmlElement elements) async {
  Database db = await MyDatabaseHelper.openConnection();
  db.transaction((tx) async {
    Batch batch = tx.batch();
    for (xml.XmlElement oi in elements.children) {      
      await Future(() {
        int id = int.parse(oi.findElements("ID").first.text);
        String name = oi.findElements("NAME").first.text;

         DatabaseHelper.insertElement(
          tx,
          id: id,
          name: name,
         );
      );
    }

    batch.commit(noResult: true);
  });
}

Это приведет к фрагментации каждого шага вашего цикла for в цикл Future, поэтому на каждом этапе ваш пользовательский интерфейс будет иметь возможность выполнять все, что ему нужно для выполнения, чтобы ваши анимации оставались плавными. Имейте в виду, однако, что это будет иметь побочный эффект замедления _parseResponse, поскольку помещение каждого шага for в очередь событий Future потребует дополнительных затрат, поэтому вы можете дополнительно оптимизировать это для своего конкретного случая использования.

person Michel Feinstein    schedule 13.07.2020

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

FlutterIsolate позволяет создавать изолятор во флаттере, который может использовать плагины флаттера.

person Jim Chiu    schedule 13.07.2020
comment
ненадежное решение, этот плагин не тестировался с большим количеством плагинов, я использовал только небольшое подмножество, такое как flutter_notification, flutter_blue и flutter_startup. - person Leres Aldtai; 08.04.2021

эта задача подходит для нативного кода ios и android, там у вас настоящая многопоточность. реализация синтаксического анализа и вставки не займет у вас много времени.

person Leres Aldtai    schedule 08.04.2021