引言
在Flutter中我们使用Future来实现异步,这种异步会造成UI卡顿吗?我们来做一个实验:
新建widget,在页面中放置一个不断转圈的progress和一个按键,按键用来触发Future方法,方法内是耗时操作。
class _TestISOWidgetState extends State<TestISOWidget> {
int _count = 0;
//耗时工作,计算偶数个数
static Future<int> 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 =
LoadBalancer.create(isoBalanceSize, IsolateRunner.spawn);
//通过iso在新的线程中执行future内容体
//R 为Future返回泛型,P 为方法入参泛型
//function 必须为 static 方法
static Future<R> loadBalanceFuture<R, P>(
FutureOr<R> Function(P argument) function,
P params,
) async {
final lb = await _loadBalancer;
return lb.run<R, P>(function, params);
}
}
ISOManger 内部维护了一个线程池,并自动实现了负载均衡。
- 调用者关注的只有 loadBalanceFuture<R,P>方法,R 为Future返回泛型,P 为方法入参泛型。需要注意一点,入参 function 必须是 static 类型。
我们把上面 demo 里的 doMockTimeConsume 方法改成 ISOManager 的调用方式:
void doMockTimeConsume() async {
// _count = await asyncCountEven(1000000000);
_count = await ISOManager.loadBalanceFuture<int, int>(
asyncCountEven, 1000000000);
setState(() {});
}
再次运行,可以看到,现在的计算并不会导致UI卡顿,完美解决问题。