flutter入门之理解Isolate及compute

Flutter Isolate详解
本文介绍如何使用Flutter中的Isolate和compute解决耗时计算导致的UI卡顿问题,并对比了不同解决方案的效果。

    【原创不易,转载请注明出处:https://blog.youkuaiyun.com/email_jade/article/details/88941434

    这篇文章将会讲解flutter中的Isolate,这有助于帮你解决某些耗时计算问题导致的卡顿。

    一 . 原始代码

    为什么要Isolate,我们先看一段比较简单的代码:

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';

class TestWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return TestWidgetState();
  }
}

class TestWidgetState extends State<TestWidget> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: Column(
          children: <Widget>[
            Container(
              width: 100,
              height: 100,
              child: CircularProgressIndicator(),
            ),
            FlatButton(
                onPressed: () async {
                  _count = countEven(1000000000);
                  setState(() {});
                },
                child: Text(
                  _count.toString(),
                )),
          ],
          mainAxisSize: MainAxisSize.min,
        ),
      ),
    );
  }

  //计算偶数的个数
  static int countEven(int num) {
    int count = 0;
    while (num > 0) {
      if (num % 2 == 0) {
        count++;
      }
      num--;
    }
    return count;
  }
}

    UI包含两个部分,一个不断转圈的progress指示器,一个按钮,当点击按钮的时候,找出比某个正整数n小的数的偶数的个数(请忽视具体算法,故意做耗时计算用,哈哈)。我们来运行一下代码看看效果:

    可以看到,本来是很流畅的转圈,当我点击按钮计算的时候,UI出现了卡顿,为什么会出现卡顿,因为我们的计算默认是在UI线程中的,当我们调用countEven的时候,这个计算需要耗时,而在这期间,UI是没有机会去调用刷新的,因此会卡顿,计算完成后,UI恢复正常刷新。

二. 使用async优化

    那么有些同学就会说了,在dart中,有async关键字,我们可以用异步计算,这样就不会影响UI的刷新了,事实真的是这样吗?我们一起来修改一下代码:

    a. 将count改为asyncCountEven

  static Future<int> asyncCountEven(int num) async{
    int count = 0;
    while (num > 0) {
      if (num % 2 == 0) {
        count++;
      }
      num--;
    }
    return count;
  }

    b. 调用:

_count = await asyncCountEven(1000000000);

    我们继续运行一下代码,看现象:

   仍然卡顿,说明异步是解决不了问题的,为什么?因为我们仍旧是在同一个UI线程中做运算,异步只是说我可以先运行其他的,等我这边有结果再返回,但是,记住,我们的计算仍旧是在这个UI线程,仍会阻塞UI的刷新,异步只是在同一个线程的并发操作。

三. 使用compute优化

    那么我们怎么解决这个问题呢,其实很简单,我们知道卡顿的原因是在同一个线程中导致的,那我们有没有办法将计算移到新的线程中呢,当然是可以的。不过在dart中,这里不是称呼线程,是Isolate,直译叫做隔离,这么古怪的名字,是因为隔离不共享数据,每个隔离中的变量都是不同的,不能相互共享。

   但是由于dart中的Isolate比较重量级,UI线程和Isolate中的数据的传输比较复杂,因此flutter为了简化用户代码,在foundation库中封装了一个轻量级compute操作,我们先看看compute,然后再来看Isolate。

    要使用compute,必须注意的有两点,一是我们的compute中运行的函数,必须是顶级函数或者是static函数,二是compute传参,只能传递一个参数,返回值也只有一个,我们先看看本例中的compute优化吧:

    真的很简单,只用在使用的时候,放到compute函数中就行了。

_count = await compute(countEven, 1000000000);

    再次运行,我们来看看效果吧:

    可以看到,现在的计算并不会导致UI卡顿,完美解决问题。

四. 使用Isolate优化

    但是,compute的使用还是有些限制,它没有办法多次返回结果,也没有办法持续性的传值计算,每次调用,相当于新建一个隔离,如果调用过多的话反而会适得其反。在某些业务下,我们可以使用compute,但是在另外一些业务下,我们只能使用dart提供的Isolate了,我们先看看Isolate在本例中的使用:

     a. 增加这两个函数

  static Future<dynamic> isolateCountEven(int num) async {
    final response = ReceivePort();
    await Isolate.spawn(countEvent2, response.sendPort);
    final sendPort = await response.first;
    final answer = ReceivePort();
    sendPort.send([answer.sendPort, num]);
    return answer.first;
  }

  static void countEvent2(SendPort port) {
    final rPort = ReceivePort();
    port.send(rPort.sendPort);
    rPort.listen((message) {
      final send = message[0] as SendPort;
      final n = message[1] as int;
      send.send(countEven(n));
    });
  }

    b. 使用

_count = await isolateCountEven(1000000000);

    相对于compute复杂了很多,效果就不贴了,和compute一样,毫无卡顿。。

五. 扩展

    isolate使用这么复杂,那么我们在那些情况下使用它呢?

    想象一下,假如你有一个业务,是使用socket和服务器连接,不断的从服务器中读取tcp流,如果业务需要拆分包的话,我们需要不断的将读取到的tcp流进行拆包分包,然后使用获取的数据来更新UI。首先,我们的socket与服务器通信,如果服务器推送消息过快,那么肯定会出现上面的情况,socket这边一直阻塞线程,导致UI刷新卡顿,所以我们需要将socket这边的业务放到隔离里面,但是,compute函数的限制我们也说了,它基本是是一次运行一次返回,但是业务要求是通过tcp的长连接不断获取服务器的数据,所以,这里我们就只能使用isolate,在UI和isolate里面使用ReceivePort进行双向通信,这样才能保证UI不卡顿的情况下仍然保持业务的完整性。

 

flutter很好,路还很长,让我们一起奋斗前行!

### FlutterIsolate 的详细用法和工作原理 #### 什么是 IsolateIsolate 是 Dart 编程语言中的一个重要概念,它表示一个独立的工作单元。每个 Isolate 都有自己的内存空间、事件循环以及任务队列[^1]。这意味着不同 Isolate 之间的数据无法直接共享,通信通常依赖消息传递机制。 #### Isolate 的创建方式 在 Flutter 和 Dart 中,可以通过两种主要方法创建一个新的 Isolate: 1. **`Isolate.spawn()` 方法** `Isolate.spawn()` 提供了一种简单的方式来启动新 Isolate 并执行指定函数。 ```dart import 'dart:isolate'; void entryPoint(SendPort sendPort) { int result = expensiveComputation(); sendPort.send(result); } Future<void> main() async { ReceivePort receivePort = ReceivePort(); await Isolate.spawn(entryPoint, receivePort.sendPort); int result = await receivePort.first as int; print('Result from isolate: $result'); } ``` 上述代码展示了如何通过 `Isolate.spawn()` 启动一个新 Isolate 来处理耗时任务,并通过消息通道接收结果。 2. **`compute()` 函数** 对于简单的场景,推荐使用 Flutter 提供的便捷工具——`compute()` 函数。该函数封装了 Isolate 的复杂细节,允许开发者轻松地将耗时任务移交给后台线程。 ```dart import 'package:flutter/foundation.dart'; import 'dart:async'; Future<int> performCalculation(int input) async { // Simulate a long-running task. await Future.delayed(Duration(seconds: 2)); return input * 2; } Future<void> main() async { final result = await compute(performCalculation, 42); print('Computed Result: $result'); // 输出 Computed Result: 84 } ``` 此处利用 `compute()` 将计算逻辑放入另一个 Isolate 执行,从而避免阻塞主线程。 #### Isolate 工作原理剖析 Dart VM 实现了多 Isolate 支持,其中每一个 Isolate 都拥有自己的堆栈结构与独立运行环境。这种设计使得即使某个 Isolate 发生崩溃也不会影响其他正在工作的 Isolate 或整个应用进程[^4]。 当调用 `Isolate.spawn()` 或者 `compute()` 时,实际上是在当前应用程序上下文中请求 Dart VM 创建额外的隔离区 (即新的 Isolate),并将目标函数及其参数序列化后发送给这个新区域去执行。完成后,再把结果反向传回原调用方所在区域。 需要注意的是,在同一进程中所有的 Isolates 共享同一个物理 CPU 核心资源池;因此即便启用了多个 Isolates,也不能超越设备硬件能力范围之外获得无限性能提升。 另外值得注意的一点在于,虽然 Isolate 能够有效解决 UI 卡顿等问题,但它并不适合频繁短时间内的大量并发操作,因为每次新建销毁都会带来一定开销成本。对于这类需求,则更倾向于采用基于单线程模型下的微任务调度方案如 Futures 结合 Async/Await 关键字来达成目的。 #### 总结 综上所述,Flutter 应用程序默认在一个单独的 Isolate 下运作,而面对可能引起界面冻结的大规模运算或长时间等待 I/O 操作等情况时,可借助 Isolate 技术手段予以缓解优化。具体实践过程中既可以选择手动管理 Isolate 生命周期的方式 (`Isolate.spawn`) ,也能采纳更为简洁高效的高级 API —— `compute()` 。不过无论采取哪种途径都需权衡利弊考虑实际应用场景特点做出合理决策[^4]。 ---
评论 14
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值