flutter: 使用 Isolate 解决 Future 卡顿

引言

在Flutter中我们使用Future来实现异步,这种异步会造成UI卡顿吗?我们来做一个实验:

新建widget,在页面中放置一个不断转圈的progress和一个按键,按键用来触发Future方法,方法内是耗时操作。

class _TestISOWidgetState extends State {
  int _count = 0;

  //耗时工作,计算偶数个数
  static Future asyncCountEven(int num) async {
    int count = 0;
    while (num > 0) {
      if (num % 2 == 0) {
        count++;
      }
      num--;
    }
    return count;
  }

  //模拟Future耗时
  void doMockTimeConsume() async {
    _count = await asyncCountEven(1000000000);
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(
            width: 100,
            height: 100,
            child: CircularProgressIndicator(),
          ),
          Text(_count.toString()),
          TextButton(
            onPressed: () {
              //触发耗时操作
              doMockTimeConsume();
            },
            child: Text('开始耗时工作'),
          )
        ],
      ),
    );
  }
}

运行发现,本来很流畅的转圈,当我点击按键后,UI出现了卡顿。

为什么会出现卡顿呢?

因为 Future 仍然是在同一个UI线程中做运算,异步只是在同一个线程的并发操作,仍会阻塞UI的刷新。


解决方法:创建新线程,使用 Isolate

Flutter team 提供了两种方式让我们将计算移到新的线程中,compute 和 Isolate:

  • compute 轻量级,但它没有办法多次返回结果,也没有办法持续性的传值计算,每次调用,相当于新建一个隔离,如果调用过多的话反而会适得其反。
  • Isolate 消耗较重,除了创建耗时,每次创建还至少需要2Mb的空间。有OOM的风险。

考虑到Isolate的消耗问题,dart team 已经为我们写好一个非常实用的 package,其中就包括 Isolate LoadBalancer 策略。

添加package引用

dependencies:
  flutter:
    sdk: flutter
  isolate: ^2.0.2

创建一个Iso工具类,如下:

abstract class ISOManager {
  //提供外部首次初始化前修改
  static int isoBalanceSize = 2;

  //LoadBalancer 2个单位的线程池
  static Future _loadBalancer =
      LoadBalancer.create(isoBalanceSize, IsolateRunner.spawn);

  //通过iso在新的线程中执行future内容体
  //R 为Future返回泛型,P 为方法入参泛型
  //function 必须为 static 方法
  static Future loadBalanceFuture(
    FutureOr Function(P argument) function,
    P params,
  ) async {
    final lb = await _loadBalancer;
    return lb.run(function, params);
  }
}

ISOManger 内部维护了一个线程池,并自动实现了负载均衡。

  • 调用者关注的只有 loadBalanceFuture方法,R 为Future返回泛型,P 为方法入参泛型。需要注意一点,入参 function 必须是 static 类型。

我们把上面 demo 里的 doMockTimeConsume 方法改成 ISOManager 的调用方式:

void doMockTimeConsume() async {
    // _count = await asyncCountEven(1000000000);
    _count = await ISOManager.loadBalanceFuture(
        asyncCountEven, 1000000000);
    setState(() {});
  }

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

你可能感兴趣的:(flutter: 使用 Isolate 解决 Future 卡顿)