Flutter 中使用 Isolate 进行耗时计算并显示 Loading 状态

Flutter 中使用 Isolate 进行耗时计算并显示 Loading 状态的完整学习笔记


🧠 Flutter Isolate 异步计算与 Loading 状态笔记

一、问题背景

在 Flutter 中,UI 渲染和逻辑代码默认运行在同一个主 Isolate(线程)中

当我们执行 大量计算或复杂循环 时,主 Isolate 会被阻塞,导致:

  • 页面卡死;
  • CircularProgressIndicator() 不旋转;
  • 用户交互卡顿。

例如:

csharp 复制代码
Future<String> performLargeCalculation() async {
  await Future.delayed(Duration(seconds: 2));
  return "Result";
}

如果在 FutureBuilder 或按钮中直接执行大计算,会出现 UI 无法刷新 的问题。

原因是:Dart 虽然是异步语言,但其异步是 事件循环模型 ,并非多线程。

一旦有耗时同步任务,就会卡住整个 UI 渲染。


二、根本原因

  • Flutter 的动画、绘制、事件分发都依赖 主 Isolate 的事件循环
  • 耗时任务阻塞 event loop;
  • 结果:UI 停止响应。

三、解决方案 ------ 使用 Isolate

🧩 概念理解

  • Isolate 是 Dart 实现真正多线程的机制;

  • 每个 Isolate 有自己的内存堆、事件循环;

  • 不共享内存,依靠 SendPort / ReceivePort 通信;

  • 非常适合执行:

    • 大量计算;
    • 数据转换;
    • JSON 解析;
    • 图像处理;
    • 训练算法等任务。

四、基本使用步骤

1️⃣ 创建通信通道

ini 复制代码
final responsePort = ReceivePort();

2️⃣ 启动新 Isolate

ini 复制代码
Isolate.spawn(_isolateEntry, _IsolateData(responsePort.sendPort));

Isolate.spawn() 的第二个参数是新 Isolate 的入口参数,必须是"可发送对象"。

3️⃣ 在 _isolateEntry 中执行耗时任务

javascript 复制代码
static void _isolateEntry(_IsolateData isolateData) {
  // 执行大量计算
  var list = [];
  for (var i = 0; i < 100000000; i++) {
    list.add('');
  }

  // 发送结果
  isolateData.responsePort.send("done");
}

4️⃣ 主线程监听结果

dart 复制代码
await for (var message in responsePort) {
  print("结果: $message");
  break;
}

五、完整示例:加载对话框 + Isolate 计算

scala 复制代码
import 'dart:isolate';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Loading Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: LoadingDemo(),
    );
  }
}

class LoadingDemo extends StatefulWidget {
  @override
  _LoadingDemoState createState() => _LoadingDemoState();
}

class _LoadingDemoState extends State<LoadingDemo> {
  Future<void> performLargeCalculation() async {
    final responsePort = ReceivePort();

    // 显示 Loading 弹窗
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (_) => const Dialog(
        child: Padding(
          padding: EdgeInsets.all(20),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              CircularProgressIndicator(),
              SizedBox(width: 20),
              Text("计算中,请稍候...")
            ],
          ),
        ),
      ),
    );

    // 启动新 Isolate
    Isolate.spawn(_isolateEntry, _IsolateData(responsePort.sendPort));

    // 等待结果
    await for (var _ in responsePort) {
      Navigator.pop(context); // 关闭弹窗
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('计算完成!')),
      );
      break;
    }
  }

  static void _isolateEntry(_IsolateData isolateData) {
    // 模拟大量计算
    var list = [];
    for (var i = 0; i < 100000000; i++) {
      list.add('');
    }

    isolateData.responsePort.send(null);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Loading Demo")),
      body: Center(
        child: ElevatedButton(
          onPressed: performLargeCalculation,
          child: const Text('开始计算'),
        ),
      ),
    );
  }
}

class _IsolateData {
  final SendPort responsePort;
  _IsolateData(this.responsePort);
}

六、向 Isolate 传递参数

如果想传递多个参数,可以创建一个封装类:

ini 复制代码
class IsolateData {
  final SendPort responsePort;
  final TrainingPlanInfo trainingPlanInfos;

  IsolateData(this.responsePort, this.trainingPlanInfos);
}

void isolateEntry(IsolateData data) {
  var responsePort = data.responsePort;
  var info = data.trainingPlanInfos;

  // 在这里使用 info
  responsePort.send("done");
}

void startIsolate() {
  final port = ReceivePort();
  final info = TrainingPlanInfo(...);

  Isolate.spawn(isolateEntry, IsolateData(port.sendPort, info));
}

七、从 Isolate 返回结果

Isolate 不共享内存,因此不能直接返回值。

正确的做法是 通过 SendPort 发送结果

scss 复制代码
void _isolateEntry(IsolateData data) {
  var result = computeHeavyTask();
  data.sendPort.send(result);  // 发送计算结果
}

void main() async {
  final receivePort = ReceivePort();

  await Isolate.spawn(_isolateEntry, IsolateData(receivePort.sendPort));

  receivePort.listen((message) {
    print('收到结果: $message');
  });
}

⚠️ 发送规则 :只能发送"可序列化"数据类型

(int、double、bool、String、List、Map、null)或这些类型的组合。


八、实战场景示例

你项目中用的写法更贴近实战👇

scss 复制代码
static void _isolateEntry(_IsolateData isolateData) {
  ...
  isolateData.responsePort.send(dayActions);
}

Future<void> _submit(BuildContext context) async {
  final responsePort = ReceivePort();

  showDialog(
    context: context,
    barrierDismissible: false,
    builder: (_) => const Dialog(
      child: Padding(
        padding: EdgeInsets.all(20),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            CircularProgressIndicator(),
            SizedBox(width: 20),
            Text("正在生成处方预览...")
          ],
        ),
      ),
    ),
  );

  Isolate.spawn(
    _isolateEntry,
    _IsolateData(responsePort.sendPort, _trainingPlanInfos),
  );

  await for (var e in responsePort) {
    Navigator.pop(context);
    // 处理 dayActions
    ...
    break;
  }
}

九、总结对比

方案 优点 缺点 场景
Future / async 实现简单 会阻塞主 Isolate 网络请求、短延时
Isolate 真正并行,不阻塞 UI 通信复杂、数据需可序列化 大量计算、循环、图像处理

🧭 小结

记忆重点:

  • Isolate = Dart 的多线程;
  • 不共享内存,只能消息传递;
  • 使用 ReceivePort / SendPort 通信;
  • 耗时任务一定要放在 Isolate 内;
  • FutureBuilder 只适合短异步,不适合重计算;
  • Loading 动画必须放在主 Isolate 控制中。

相关推荐
黄毛火烧雪下12 小时前
(二)Flutter插件之Android插件开发
android·flutter
明月与玄武13 小时前
Melos 使用指南:Flutter / Dart 多包管理工具!
flutter·melos 使用指南·dart 多包管理工具
shr007_1 天前
flutter 鸿蒙
flutter·华为·harmonyos
Bryce李小白1 天前
Flutter 与原生混合编程
flutter
wahkim1 天前
移动端开发工具集锦
flutter·ios·android studio·swift
傅里叶1 天前
Flutter / Dart 多包管理工具 —— Melos 使用指南
flutter
西西学代码1 天前
Flutter---生命周期
flutter
LiWeNHuI1 天前
Flutter开发:发布插件到Pub
flutter
衿璃1 天前
Flutter应用架构设计的思考
前端·flutter