flutter“多線程”isolate小學生級理解

我們小學二年級就學過:

Dart 是單線程的并且 Flutter 依賴于 Dart。

但是如果我們要在主線程做一些計算量大的操作,就必然會阻塞線程,使UI更新卡頓甚至卡死。那怎么辦呢?

好消息是 Dart 為我們提供了 isolate,isolate 跟線程差不多,他是 Dart 中的線程。

isolate 與線程的區(qū)別就是線程與線程之間是共享內(nèi)存的,而 isolate 和 isolate 之間是不共享的,所以叫 isolate (隔離)。

在flutter 里面主線程就是主 isolate 。如果我們要進行一些大計算量的操作就應該啟動一個新的 isolate。

那么應該如何來開啟呢?在此之前我想講個故事。

小紅與小藍的故事

有個舞者叫小紅,她正在給觀眾跳舞。

跳舞的小紅

但是觀眾卻要求她一邊跳舞一邊計算一個數(shù)字里面有多少個偶數(shù)。于是。。。

異步計算

這那行??!你必須給我一邊跳一邊算,算的時候不能停下來!

于是小紅沒辦法,決定在異世界召喚一個小藍來幫她計算。

image

但是小紅和小藍被異世界的屏障隔離,她們也沒有思想共通的超能力。只能在召喚的同時傳送一個包裹給小藍。

小藍被召喚出來后收到包裹,打開后里面是要計算的數(shù)字,就開始計算,但是計算后要怎么把結果告訴小紅呢?

上帝做了一個約定,在小紅召喚小藍的時候,會變一個傳送裝置(傳送裝置可以用來接收包裹,還可以生成一個專屬發(fā)送器)。然后把發(fā)送器傳送給小藍。

當小藍被召喚出來后,打開包裹,里面是一個發(fā)送器,然后小藍自己也變一個傳送裝置,生成一個發(fā)送器,然后用小紅的發(fā)送器把小藍的發(fā)送器發(fā)送給小紅。發(fā)送出去后就坐在傳送裝置旁邊等包裹。

當小紅收到小藍的發(fā)送器后就把小藍的發(fā)送器存起來。

當有觀眾要求小紅計算時,就分神一邊跳舞,一邊生成一個臨時傳送裝置,把要計算的數(shù)字和臨時發(fā)送器打包成一個包裹,然后通過小藍的發(fā)送器發(fā)給小藍,等傳送裝置出結果。因為不用自己算了,只是等,所以跳舞的時候線條也流暢了,動作也優(yōu)美了。

說回小藍這邊,小藍看到傳送裝置出現(xiàn)了一個包裹,里面是一個臨時發(fā)送器,還有一個數(shù)字。于是小藍就開始計算。算好了就用臨時發(fā)送器把數(shù)字發(fā)送給小紅。

小紅收到結果后就告訴觀眾,那個數(shù)字有多少個偶數(shù)。

故事結束,第一次嘗試這樣的風格,可能寫得有點爛,不過結合代碼來看的話,應該還是挺容易理解的。

代碼實踐

首先我們先讓小紅跳起舞來。

  @override
  void initState() {
    controller =
        AnimationController(duration: Duration(seconds: 3), vsync: this);
    animation = Tween<double>(begin: 0, end: pi * 2).animate(controller);
    controller.repeat();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            AnimatedBuilder(
                animation: animation,
                child: Text(
                  '小紅',
                  style: TextStyle(fontSize: 30, color: Colors.red),
                ),
                builder: (context, child) {
                  return Transform.rotate(
                    angle: animation.value,
                    child: child,
                  );
                }),
          ],
        ),
      ),
    );
  }

接下來讓小紅計算一個數(shù)字里面有多少個偶數(shù)。

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text(widget.title),
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          AnimatedBuilder(
              animation: animation,
              child: Text(
                '小紅',
                style: TextStyle(fontSize: 30, color: Colors.red),
              ),
              builder: (context, child) {
                return Transform.rotate(
                  angle: animation.value,
                  child: child,
                );
              }),
          Padding(
            padding: EdgeInsets.only(top: 16),
            child:
                RaisedButton(onPressed: count, child: Text('異步計算偶數(shù)的個數(shù)')),
          ),
          Text(result)
        ],
      ),
    ),
  );
}

int getRandom() {
  int a =  Random().nextInt(100);
  return a + 1000000000;
}

// 異步計算
count() async {
  int random = getRandom();
  int r = countEven(random);
  setState(() {
    this.result = '${random.toString()}有${r.toString()}個偶數(shù)';
  });
}

//計算偶數(shù)的個數(shù)
int countEven(num num) {
  int count = 0;
  while (num > 0) {
    if (num % 2 == 0) {
      count++;
    }
    num--;
  }
  return count;
}

這就是效果

異步計算

定義 isolate

我愿稱之為召喚小藍。

首先我們要知道兩個類:

ReceivePort
SendPort

ReceivePort 就是故事中的傳送裝置,而 SendPort 則是發(fā)送器。

我們可以通過以下方式創(chuàng)建傳送裝置和對應的發(fā)送器

ReceivePort receive = ReceivePort();
SendPort sender = receive.sendPort;

好的,知道這些就行了。接下來我們定義小藍。

// 消息包裹,用來存臨時發(fā)送器和消息
class MessagePackage {
  SendPort sender; // 臨時發(fā)送器
  dynamic msg; // 消息

  MessagePackage(this.sender, this.msg);

}

// 我是小藍,負責計算偶數(shù)的個數(shù),我必須是頂級函數(shù)
blueCounter(SendPort redSendPort) {
  // 創(chuàng)建小藍的傳送裝置
  ReceivePort blueReceivePort = ReceivePort();
  // 用小紅的發(fā)送器把小藍的發(fā)送器發(fā)送出去
  redSendPort.send(blueReceivePort.sendPort);
  // 監(jiān)聽小藍的傳送裝置,等待小紅叫小藍計算
  blueReceivePort.listen((package) {
    // 這里的msg是dynamic,需要轉(zhuǎn)換成 MessagePackage 類,上面自己定義的包裹封裝類
    MessagePackage _msg = package as MessagePackage;
    // 小藍開始計算
    int r = countEven(_msg.msg as num);
    // 計算好了用小紅的臨時發(fā)送器告訴小紅
    _msg.sender.send(r);
  });
}

創(chuàng)建isolate

工具人小藍定義好了,我們?nèi)コ跏蓟?召喚)一下小藍。

// 創(chuàng)建isolate
void createIsolate() async {
  // 創(chuàng)建小紅的接收器,用來接收小藍的發(fā)送器
  ReceivePort redReceive = ReceivePort();
  // 創(chuàng)建 isolate, 并且把小紅的發(fā)送器傳給小藍
  isolate = await Isolate.spawn<SendPort>(blueCounter, redReceive.sendPort);
  // 等待小藍把發(fā)送器發(fā)送給小紅
  blueSender = await redReceive.first;
  // 不用了記得關閉接收器
  redReceive.close();
}


@override
void initState() {
  controller =
      AnimationController(duration: Duration(seconds: 3), vsync: this);
  animation = Tween<double>(begin: 0, end: pi * 2).animate(controller);
  controller.repeat();
  // 在initState中初始化isolate
  createIsolate();
  super.initState();
}

現(xiàn)在小藍已經(jīng)被召喚了出來,并且和小紅建立了通信。

使isolate 開始計算

接下來我們就讓小紅開始計算吧。


@override
Widget build(BuildContext context) {
  ...
  Padding(
      padding: EdgeInsets.only(top: 16),
      child: RaisedButton(
          onPressed: isolateCount, child: Text('isolate計算偶數(shù)的個數(shù)')
      ),
  ),
  ...
}

// 開啟isolate計算
isolateCount() async {
  // 獲取要計算的數(shù)字
  int random = getRandom();
  // 創(chuàng)建一個臨時傳送裝置
  ReceivePort _temp = ReceivePort();
  // 用小藍的發(fā)送裝置發(fā)送一個消息包裹,里面是臨時傳送裝置的發(fā)送器和要計算的數(shù)字
  blueSender.send(MessagePackage(_temp.sendPort, random));
  // 等待臨時傳送裝置返回計算結果
  int r = await _temp.first;
  // 不用了記得關閉臨時接收器
  _temp.close();
  // 把計算結果告訴觀眾
  setState(() {
    this.result = '${random.toString()}有${r.toString()}個偶數(shù)';
  });
}

需要注意的是當使用完了 isolate 記得要銷毀。


@override
void dispose() {
  // 銷毀 isolate
  isolate?.kill(priority: Isolate.immediate);
  super.dispose();
}

OK,到這里相信你已經(jīng)看懂并會使用 isolate 了。
我們來看看效果圖。

isolate計算

使用 computed

到這里還沒完,也許你會覺得太麻煩了。是的這樣用 isolate 太麻煩了,isolate 被設計成可以多次輸入輸出,而我們做這個計算只有一次輸入和輸出,那么我們就可以用 flutter 為我們提供的 computed 來完成計算操作,它是對 isolate 的一個封裝。下面看看怎么用吧!敲簡單的。

import 'dart:isolate';

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'isolate Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'isolate Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;
  String result = '';
  SendPort blueSender;
  Isolate isolate;

  @override
  void initState() {
    controller =
        AnimationController(duration: Duration(seconds: 3), vsync: this);
    animation = Tween<double>(begin: 0, end: pi * 2).animate(controller);
    controller.repeat();
    // 在initState中初始化isolate
    createIsolate();
    super.initState();
  }

  @override
  void dispose() {
    // 銷毀 isolate
    isolate?.kill(priority: Isolate.immediate);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            AnimatedBuilder(
                animation: animation,
                child: Text(
                  '小紅',
                  style: TextStyle(fontSize: 30, color: Colors.red),
                ),
                builder: (context, child) {
                  return Transform.rotate(
                    angle: animation.value,
                    child: child,
                  );
                }),
            Padding(
              padding: EdgeInsets.only(top: 16),
              child: RaisedButton(onPressed: count, child: Text('異步計算偶數(shù)的個數(shù)')),
            ),
            Padding(
              padding: EdgeInsets.only(top: 16),
              child: RaisedButton(
                  onPressed: isolateCount, child: Text('isolate計算偶數(shù)的個數(shù)')),
            ),
            Padding(
              padding: EdgeInsets.only(top: 16),
              child: RaisedButton(
                  onPressed: computeCount, child: Text('compute計算偶數(shù)的個數(shù)')),
            ),
            Text(result)
          ],
        ),
      ),
    );
  }

  // 獲取隨機數(shù)
  int getRandom() {
    int a = Random().nextInt(100);
    return a + 1000000000;
  }

  // 異步計算
  count() async {
    int random = getRandom();
    int r = countEven(random);
    setState(() {
      this.result = '${random.toString()}有${r.toString()}個偶數(shù)';
    });
  }

  // 創(chuàng)建isolate
  void createIsolate() async {
    // 創(chuàng)建小紅的接收器,用來接收小藍的發(fā)送器
    ReceivePort redReceive = ReceivePort();
    // 創(chuàng)建 isolate, 并且把小紅的發(fā)送器傳給小藍
    isolate = await Isolate.spawn<SendPort>(blueCounter, redReceive.sendPort);
    // 等待小藍把發(fā)送器發(fā)送給小紅
    blueSender = await redReceive.first;
    // 不用了記得關閉接收器
    redReceive.close();
  }

  // 利用compute計算
  computeCount() async {
    int random = getRandom();
    // compute 的回調(diào)函數(shù)必須是頂級函數(shù)或者static函數(shù)
    int r = await compute(countEven, random);
    setState(() {
      this.result = '${random.toString()}有${r.toString()}個偶數(shù)';
    });
  }

  // 開啟isolate計算
  isolateCount() async {
    // 獲取要計算的數(shù)字
    int random = getRandom();
    // 創(chuàng)建一個臨時傳送裝置
    ReceivePort _temp = ReceivePort();
    // 用小藍的發(fā)送裝置發(fā)送一個消息包裹,里面是臨時傳送裝置的發(fā)送器和要計算的數(shù)字
    blueSender.send(MessagePackage(_temp.sendPort, random));
    // 等待臨時傳送裝置返回計算結果
    int r = await _temp.first;
    _temp.close();
    // 把計算結果告訴觀眾
    setState(() {
      this.result = '${random.toString()}有${r.toString()}個偶數(shù)';
    });
  }
}

// 消息包裹,用來存臨時發(fā)送器和消息
class MessagePackage {
  SendPort sender; // 臨時發(fā)送器
  dynamic msg; // 消息

  MessagePackage(this.sender, this.msg);
}

// 我是小藍,負責計算偶數(shù)的個數(shù),我必須是頂級函數(shù)
blueCounter(SendPort redSendPort) {
  // 創(chuàng)建小藍的傳送裝置
  ReceivePort blueReceivePort = ReceivePort();
  // 用小紅的發(fā)送器把小藍的發(fā)送器發(fā)送出去
  redSendPort.send(blueReceivePort.sendPort);
  // 監(jiān)聽小藍的傳送裝置,等待小紅叫小藍計算
  blueReceivePort.listen((package) {
    // 這里的msg是dynamic,需要轉(zhuǎn)換成 MessagePackage 類,上面自己定義的包裹封裝類
    MessagePackage _msg = package as MessagePackage;
    // 小藍開始計算
    int r = countEven(_msg.msg as num);
    // 計算好了用小紅的臨時發(fā)送器告訴小紅
    _msg.sender.send(r);
  });
}

//計算偶數(shù)的個數(shù),此函數(shù)需要大量的計算資源和時間
int countEven(num num) {
  int count = 0;
  while (num > 0) {
    if (num % 2 == 0) {
      count++;
    }
    num--;
  }
  return count;
}
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 該文章屬于劉小壯原創(chuàng),轉(zhuǎn)載請注明:劉小壯[http://www.itdecent.cn/u/2de707c93d...
    劉小壯閱讀 30,791評論 12 88
  • Dart之異步編程 本文轉(zhuǎn)載自https://www.cnblogs.com/lxlx1798/p/1112656...
    緣煥閱讀 1,431評論 0 1
  • 風吹動香樟樹的樹葉,空氣里傳來熟悉的樹葉的清香,林子戴著耳機躺在足球場的草坪上聽歌,午后的陽光格外溫和,即便如此,...
    拾月言人閱讀 251評論 0 0
  • 這天女貞路四號的早餐桌上又起了爭執(zhí)。一大早,弗農(nóng)·德思禮先生就被他的外甥哈利屋里的一陣高校怪聲吵醒了?!斑@星期是...
    聶亞卓閱讀 182評論 0 0
  • 時間安排 周一周三周五 周二四及奶奶出差
    chzhbob閱讀 261評論 0 0

友情鏈接更多精彩內(nèi)容