Flutter基础——线程和异步

Posted by lingjye on July 18, 2019

之前在yi提到过,Dart是单线程执行模型。但是它支持Isolate(一种让 Dart 代码运行在其他线程的方式)、时间循环和异步编程。除非自己创建一个Isolate,否则Dart代码会运行在主线程,并由event loop驱动。

Dart的单线程模型并不意味着一定是阻塞操作。可以使用Dart提供的异步工具(async/await),来实现异步操作。

实现一个列表,数据请求后刷新界面,需要在pubspec.yml中添加http:引入该http包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class AsyncSampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Title',
      theme: ThemeData(
        primarySwatch: Colors.blue
      ),
      home: AsyncSampleAppHomePage(),
    );
  }
}

class AsyncSampleAppHomePage extends StatefulWidget {
  AsyncSampleAppHomePage({Key key}) : super(key: key);

  _AsyncSampleAppHomePageState createState() => _AsyncSampleAppHomePageState();
}

class _AsyncSampleAppHomePageState extends State<AsyncSampleAppHomePage> {
  List widgets = [];

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    loadData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('异步'),        
      ),
      // 创建一个ListView
      body: ListView.builder(
        itemCount: widgets.length,
        itemBuilder: (BuildContext context, int position){
          return getRow(position);
        },
      ),
    );
  }
  // 返回一个列表子元素,类似UITableViewCell
  Widget getRow(int i) {
    return Padding(
      padding: EdgeInsets.all(10.0),
      child: Text('Row ${widgets[i]["title"]}'),
    );
  }
  // 网络请求,异步
  void loadData() async {
    String dataUrl = 'https://jsonplaceholder.typicode.com/posts';
	// 获取到数据后解析
	http.Response response = await http.get(dataUrl);
	setState(() {
		widgets = json.decode(response.body);
	});
  }
}

由于Flutter是单线程并且跑着一个event loop的,所以不必为线程管理或是开启后台线程而操心。如果正在做I/O操作(访问磁盘或网络请求),使用async/await就可以。如果让CPU执行计算密集型任务,需要使用Isolate来避免阻塞event loop。

在Flutter中,使用Isolate来发挥多核CPU的优势来处理长期执行的或计算密集型的任务。Isolates是分离的运行线程,并且不和主线程的内存堆共享内容,所以不能访问主线程中的变量或者使用setState()来更新UI。Isolates不能共享内存。

使用Isolate更新UI示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
class IsolateSampleApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: IsolateSampleAppPage(),
    );
  }
}

class IsolateSampleAppPage extends StatefulWidget {
  IsolateSampleAppPage({Key key}) : super(key: key);

  _IsolateSampleAppPageState createState() => _IsolateSampleAppPageState();
}

class _IsolateSampleAppPageState extends State<IsolateSampleAppPage> {
  List widgets = [];
  bool loading = true;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    loadData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Isolate'),
        backgroundColor: Colors.blue,
      ),
      body: getBody(),
    );
  }

  showLoadingDialog() {
    return loading;
  }

  getBody() {
    if (showLoadingDialog()) {
      return getProgressDialog();
    }
    return getListView();
  }

  getProgressDialog() {
    return Center(
      child: CircularProgressIndicator(),
    );
  }

  Widget getListView() {
    return ListView.builder(
      itemCount: widgets.length,
      itemBuilder: (BuildContext context, int position) {
        return getRow(position);
      },
    );
  }

  Widget getRow(int i) {
    return Padding(
      padding: EdgeInsets.all(10),
      child: Text(widgets[i]['title']),
    );
  }

  loadData() async {
    ReceivePort receivePort = ReceivePort();
    await Isolate.spawn(dataLoader, receivePort.sendPort);
    // The 'echo' isolate sends its SendPort as the first message
    SendPort sendPort = await receivePort.first;

    List msg = await sendReceive(sendPort, 'https://jsonplaceholder.typicode.com/posts');

    setState(() {
      widgets = msg;
      loading = false;
    });
  }

  // 进入Isolate
  static dataLoader(SendPort sendPort) async {
    // 打开接收消息端口
    ReceivePort port = ReceivePort();
    // 通知Isolate
    sendPort.send(port.sendPort);

    await for (var msg in port) {
      String data = msg[0];
      SendPort replyTo = msg[1];

      String dataUrl = data;
      http.Response response = await http.get(dataUrl);
      replyTo.send(json.decode(response.body));
    }
  }

  Future sendReceive(SendPort port, msg) {
    ReceivePort response = ReceivePort();
    port.send([msg, response.sendPort]);
    return response.first;
  }
}

本文Demo