Flutter项目如何将任务转移到后台线程?

2019年11月6日11:34:13 发表评论 323 views

Android 中,当你想要访问一个网络资源却又不想阻塞主线程并避免 ANR 的时候,你一般会将任务放到一个后台线程中运行。例如,你可以使用一个 AsyncTask、一个 LiveData、一个 IntentService、一个 JobScheduler 任务或者通过 RxJava 的管道用调度器将任务切换到后台线程中。

由于 Flutter 是单线程并且运行一个事件循环(类似 ),你无须担心线程的管理以及后台线程的创建。如果你在执行和 I/O 绑定的任务,例如存储访问或者网络请求,那么你可以安全地使用 async/await,并无后顾之忧。再例如,你需要执行消耗 CPU 的计算密集型工作,那么你可以将其转移到一个 Isolate 上以避免阻塞事件循环,就像你在 Android 中会将任何任务放到主线程之外一样。

对于和 I/O 绑定的任务,将方法声明为 async 方法,并在方法内 await 一个长时间运行的任务:

content_copy

loadData() async {
 String dataURL = "";
 http.Response response = await http.get(dataURL);
 setState(() {
 widgets = ();
 });
}

这就是你一般应该如何执行网络和数据库操作,它们都属于 I/O 操作。

在 Android 中,当你继承 AsyncTask 的时候,你一般会覆写三个方法,onPreExecute()、doInBackground() 和 onPostExecute()。Flutter 中没有对应的 API,你只需要 await 一个耗时方法调用, Dart 的事件循环就会帮你处理剩下的事情。

然而,有时候你可能需要处理大量的数据并挂起你的 UI。在 Flutter 中,可以通过使用 Isolate 来利用多核处理器的优势执行耗时或计算密集的任务。

Isolate 是独立执行的线程,不会和主执行内存堆分享内存。这意味着你无法访问主线程的变量,或者调用 setState() 更新 UI。不同于 Android 中的线程,Isolate 如其名所示,它们无法分享内存(例如通过静态变量的形式)。

下面的例子展示了一个简单的 Isolate 是如何将数据分享给主线程来更新 UI 的。

content_copy

loadData() async {
 ReceivePort receivePort = ReceivePort();
 await Isolate.spawn(dataLoader, );
 // The 'echo' isolate sends its SendPort as the first message.
 SendPort sendPort = await receivePort.first;
 List msg = await sendReceive(sendPort, "");
 setState(() {
 widgets = msg;
 });
}
// The entry point for the isolate.
static dataLoader(SendPort sendPort) async {
 // Open the ReceivePort for incoming messages.
 ReceivePort port = ReceivePort();
 // Notify any other isolates what port this isolate listens to.
 sendPort.send();
 await for (var msg in port) {
 String data = msg[0];
 SendPort replyTo = msg[1];
 String dataURL = data;
 http.Response response = await http.get(dataURL);
 // Lots of JSON to parse
 replyTo.send(());
 }
}
Future sendReceive(SendPort port, msg) {
 ReceivePort response = ReceivePort();
 ([msg, ]);
 return response.first;
}

这里的 dataLoader() 就是运行在自己独立执行线程内的 Isolate。在 Isolate 中你可以执行更多的 CPU 密集型操作(例如解析一个大的 JSON 数据),或者执行计算密集型的数学运算,例如加密或信号处理。

你可以运行下面这个完整的例子:

content_copy

import 'dart:convert';
import 'package:flutter/';
import 'package:http/' as http;
import 'dart:async';
import 'dart:isolate';
void main() {
 runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
 return MaterialApp(
 title: 'Sample App',
 theme: ThemeData(
 primarySwatch: ,
 ),
 home: SampleAppPage(),
 );
 }
}
class SampleAppPage extends StatefulWidget {
 SampleAppPage({Key key}) : super(key: key);
 @override
 _SampleAppPageState createState() =< _SampleAppPageState();
}
class _SampleAppPageState extends State>SampleAppPage< {
 List widgets = [];
 @override
 void initState() {
 ();
 loadData();
 }
 showLoadingDialog() {
 if (widgets.length == 0) {
 return true;
 }
 return false;
 }
 getBody() {
 if (showLoadingDialog()) {
 return getProgressDialog();
 } else {
 return getListView();
 }
 }
 getProgressDialog() {
 return Center(child: CircularProgressIndicator());
 }
 @override
 Widget build(BuildContext context) {
 return Scaffold(
 appBar: AppBar(
 title: Text("Sample App"),
 ),
 body: getBody());
 }
 ListView getListView() =< ListView.builder(
 itemCount: widgets.length,
 itemBuilder: (BuildContext context, int position) {
 return getRow(position);
 });
 Widget getRow(int i) {
 return Padding(padding: (), child: Text("Row ${widgets[i]["title"]}"));
 }
 loadData() async {
 ReceivePort receivePort = ReceivePort();
 await Isolate.spawn(dataLoader, );
 // The 'echo' isolate sends its SendPort as the first message
 SendPort sendPort = await receivePort.first;
 List msg = await sendReceive(sendPort, "");
 setState(() {
 widgets = msg;
 });
 }
 // the entry point for the isolate
 static dataLoader(SendPort sendPort) async {
 // Open the ReceivePort for incoming messages.
 ReceivePort port = ReceivePort();
 // Notify any other isolates what port this isolate listens to.
 sendPort.send();
 await for (var msg in port) {
 String data = msg[0];
 SendPort replyTo = msg[1];
 String dataURL = data;
 http.Response response = await http.get(dataURL);
 // Lots of JSON to parse
 replyTo.send(());
 }
 }
 Future sendReceive(SendPort port, msg) {
 ReceivePort response = ReceivePort();
 ([msg, ]);
 return response.first;
 }
}

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: