sqlite - Flutter:在进行大量数据库操作时避免 UI 卡住

标签 sqlite flutter dart-async dart-isolates

更新 (2020 年 7 月 15 日)

mFeinstein's response, for now, is the only answer which gives me the first acceptable solution.



问题
我不得不问你做我想做的事情的最佳方法是什么:
  • 以异步模式调用 Web 服务
  • 解析响应
  • 执行海量数据库操作

  • 所有这一切都没有卡住进度动画,就像不确定的进度条。
    第一点和第二点没有问题。问题出现在第三次,当大量的数据库插入在起作用时。我还不明白实现这些东西的正确方法是什么。
    一些伪代码用于澄清
    UI(显示对话框并运行进度条...)
    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() 中的第一个,没有任何 react 。

    最佳答案

    问题是 Flutter 是单线程的,所以一旦你运行了一个繁重的进程,你的单线程就会阻塞其他任何东西。
    解决方案是聪明地使用单线程。
    Dart 将有一个事件队列,其中包含一堆 Futures等待处理。一旦 Dart 引擎看到 await它会让另一个 Future捕获单线程并让它运行。这样一来,一个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 ,因此在每一步,您的 UI 都将有机会执行它需要执行的任何操作,以保持动画流畅。但请记住,这会产生减慢速度的副作用 _parseResponse作为把每个 for步入Future事件队列将产生额外费用,因此您可能希望针对您的特定用例进一步优化它。

    关于sqlite - Flutter:在进行大量数据库操作时避免 UI 卡住,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62087298/

    相关文章:

    android - 未处理的异常:DioError PONSE]:Http状态错误[500]

    dart - 关于使用 Future 和 Completers 的问题

    dart - dart2js 之后异步函数中两个 keyCode 的比较不正确

    c++ - 用于 C++ 的跨平台 Sqlite3 包装器

    flutter - 我可以将TabBar中的标签向左而不是中心对齐吗?

    Flutter - 水平滚动旁边的固定按钮

    flutter - async await 没有按预期工作,它必须返回我 future 的结果

    android - 在 Android Sqlite 中,ROWID 与主键相同吗?

    Android SQLite 数据库 : Creating db at runtime or using prepopulated db?

    python - 当没有这样的命名列时,SQLite 给出找不到列的错误