flutter入門之理解Isolate及compute

一 . 原始代碼
為什么要Isolate,我們先看一段比較簡單的代碼:

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
 
class TestWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return TestWidgetState();
  }
}
 
class TestWidgetState extends State<TestWidget> {
  int _count = 0;
 
  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: Column(
          children: <Widget>[
            Container(
              width: 100,
              height: 100,
              child: CircularProgressIndicator(),
            ),
            FlatButton(
                onPressed: () async {
                  _count = countEven(1000000000);
                  setState(() {});
                },
                child: Text(
                  _count.toString(),
                )),
          ],
          mainAxisSize: MainAxisSize.min,
        ),
      ),
    );
  }
 
  //計(jì)算偶數(shù)的個(gè)數(shù)
  static int countEven(int num) {
    int count = 0;
    while (num > 0) {
      if (num % 2 == 0) {
        count++;
      }
      num--;
    }
    return count;
  }
}

UI包含兩個(gè)部分,一個(gè)不斷轉(zhuǎn)圈的progress指示器骑歹,一個(gè)按鈕,當(dāng)點(diǎn)擊按鈕的時(shí)候墨微,找出比某個(gè)正整數(shù)n小的數(shù)的偶數(shù)的個(gè)數(shù)(請忽視具體算法道媚,故意做耗時(shí)計(jì)算用,哈哈)欢嘿。我們來運(yùn)行一下代碼看看效果:



可以看到衰琐,本來是很流暢的轉(zhuǎn)圈,當(dāng)我點(diǎn)擊按鈕計(jì)算的時(shí)候炼蹦,UI出現(xiàn)了卡頓羡宙,為什么會(huì)出現(xiàn)卡頓,因?yàn)槲覀兊挠?jì)算默認(rèn)是在UI線程中的掐隐,當(dāng)我們調(diào)用countEven的時(shí)候狗热,這個(gè)計(jì)算需要耗時(shí),而在這期間虑省,UI是沒有機(jī)會(huì)去調(diào)用刷新的匿刮,因此會(huì)卡頓,計(jì)算完成后探颈,UI恢復(fù)正常刷新熟丸。

二. 使用async優(yōu)化
那么有些同學(xué)就會(huì)說了,在dart中伪节,有async關(guān)鍵字光羞,我們可以用異步計(jì)算,這樣就不會(huì)影響UI的刷新了怀大,事實(shí)真的是這樣嗎纱兑?我們一起來修改一下代碼:

a. 將count改為asyncCountEven
  static Future<int> asyncCountEven(int num) async{
    int count = 0;
    while (num > 0) {
      if (num % 2 == 0) {
        count++;
      }
      num--;
    }
    return count;
  }
b. 調(diào)用:
_count = await asyncCountEven(1000000000);
我們繼續(xù)運(yùn)行一下代碼,看現(xiàn)象:

仍然卡頓化借,說明異步是解決不了問題的潜慎,為什么?因?yàn)槲覀內(nèi)耘f是在同一個(gè)UI線程中做運(yùn)算,異步只是說我可以先運(yùn)行其他的铐炫,等我這邊有結(jié)果再返回垒手,但是,記住驳遵,我們的計(jì)算仍舊是在這個(gè)UI線程淫奔,仍會(huì)阻塞UI的刷新,異步只是在同一個(gè)線程的并發(fā)操作堤结。

三. 使用compute優(yōu)化
那么我們怎么解決這個(gè)問題呢,其實(shí)很簡單鸭丛,我們知道卡頓的原因是在同一個(gè)線程中導(dǎo)致的竞穷,那我們有沒有辦法將計(jì)算移到新的線程中呢,當(dāng)然是可以的鳞溉。不過在dart中瘾带,這里不是稱呼線程,是Isolate熟菲,直譯叫做隔離看政,這么古怪的名字,是因?yàn)楦綦x不共享數(shù)據(jù)抄罕,每個(gè)隔離中的變量都是不同的允蚣,不能相互共享。

但是由于dart中的Isolate比較重量級呆贿,UI線程和Isolate中的數(shù)據(jù)的傳輸比較復(fù)雜嚷兔,因此flutter為了簡化用戶代碼,在foundation庫中封裝了一個(gè)輕量級compute操作做入,我們先看看compute冒晰,然后再來看Isolate。

要使用compute竟块,必須注意的有兩點(diǎn)壶运,一是我們的compute中運(yùn)行的函數(shù),必須是頂級函數(shù)或者是static函數(shù)浪秘,二是compute傳參蒋情,只能傳遞一個(gè)參數(shù),返回值也只有一個(gè)秫逝,我們先看看本例中的compute優(yōu)化吧:

真的很簡單恕出,只用在使用的時(shí)候,放到compute函數(shù)中就行了违帆。
_count = await compute(countEven, 1000000000);
再次運(yùn)行浙巫,我們來看看效果吧:



可以看到,現(xiàn)在的計(jì)算并不會(huì)導(dǎo)致UI卡頓,完美解決問題的畴。

四. 使用Isolate優(yōu)化
但是渊抄,compute的使用還是有些限制,它沒有辦法多次返回結(jié)果丧裁,也沒有辦法持續(xù)性的傳值計(jì)算护桦,每次調(diào)用,相當(dāng)于新建一個(gè)隔離煎娇,如果調(diào)用過多的話反而會(huì)適得其反二庵。在某些業(yè)務(wù)下,我們可以使用compute缓呛,但是在另外一些業(yè)務(wù)下催享,我們只能使用dart提供的Isolate了,我們先看看Isolate在本例中的使用:

 a. 增加這兩個(gè)函數(shù)
  static Future<dynamic> isolateCountEven(int num) async {
    final response = ReceivePort();
    await Isolate.spawn(countEvent2, response.sendPort);
    final sendPort = await response.first;
    final answer = ReceivePort();
    sendPort.send([answer.sendPort, num]);
    return answer.first;
  }
 
  static void countEvent2(SendPort port) {
    final rPort = ReceivePort();
    port.send(rPort.sendPort);
    rPort.listen((message) {
      final send = message[0] as SendPort;
      final n = message[1] as int;
      send.send(countEven(n));
    });
  }
b. 使用
_count = await isolateCountEven(1000000000);
相對于compute復(fù)雜了很多哟绊,效果就不貼了因妙,和compute一樣,毫無卡頓票髓。攀涵。

代價(jià)是什么

對于我們來說,其實(shí)是把多線程當(dāng)做一種計(jì)算資源來使用的洽沟。我們可以通過創(chuàng)建新的 isolate 計(jì)算 heavy work以故,從而減輕 UI 線程的負(fù)擔(dān)。但是這樣做的代價(jià)是什么呢玲躯?

時(shí)間

通常來說据德,當(dāng)我們使用多線程計(jì)算的時(shí)候,整個(gè)計(jì)算的時(shí)間會(huì)比單線程要多跷车,額外的耗時(shí)是什么呢棘利?

  • 創(chuàng)建 Isolate
  • Copy Message

當(dāng)我們按照上面的代碼執(zhí)行一段多線程代碼時(shí),經(jīng)歷了 isolate 的創(chuàng)建以及銷毀過程朽缴。下面是一種我們在解析 json 中這樣編寫代碼可能的方式善玫。

  static BSModel toBSModel(String json){}

  parsingModelList(List<String> jsonList) async{
    for(var model in jsonList){
      BSModel m = await compute(toBSModel, model);
    }
  }
復(fù)制代碼

在解析 json 的時(shí)候,我們可能通過 compute 把解析任務(wù)放在新的 isolate 中完成密强,然后把值傳過來茅郎。這時(shí)候我們會(huì)發(fā)現(xiàn),整個(gè)解析會(huì)變得異常的慢或渤。這是由于我們每次創(chuàng)建 BSModel 的時(shí)候都經(jīng)歷了一次 isolate 的創(chuàng)建以及銷毀過程系冗。這將會(huì)耗費(fèi)約 50-150ms 的時(shí)間。

在這之中薪鹦,我們傳遞 data 也經(jīng)歷了 Network -> Main Isolate -> New Isolate (result) -> Main Isolate掌敬,多出來兩次 copy 的操作惯豆。如果我們是在 Main 線程之外的 isolate 下載的數(shù)據(jù),那么就可以直接在該線程進(jìn)行解析奔害,最后只需要傳回 Main Isolate 即可楷兽,省下了一次 copy 操作。(Network -> New Isolate (result)-> Main Isolate)

空間

Isolate 實(shí)際上是比較重的华临,每當(dāng)我們創(chuàng)建出來一個(gè)新的 Isolate 至少需要 2mb 左右的空間甚至更多芯杀,取決于我們具體 isolate 的用途。

OOM 風(fēng)險(xiǎn)

我們可能會(huì)使用 message 傳遞 data 或 file雅潭。而實(shí)際上我們傳遞的 message 是經(jīng)歷了一次 copy 過程的揭厚,這其實(shí)就可能存在著 OOM 的風(fēng)險(xiǎn)。

如果說我們想要返回一個(gè) 2GB 的 data寻馏,在 iPhone X(3GB ram)上棋弥,我們是無法完成 message 的傳遞操作的。

Tips

上面已經(jīng)介紹了使用 isolate 進(jìn)行多線程操作會(huì)有一些額外的 cost诚欠,那么是否可以通過一些手段減少這些消耗呢。我個(gè)人建議從兩個(gè)方向上入手漾岳。

  • 減少 isolate 創(chuàng)建所帶來的消耗轰绵。
  • 減少 message copy 次數(shù),以及大小尼荆。

使用 LoadBalancer

如何減少 isolate 創(chuàng)建所帶來的消耗呢左腔。自然一個(gè)想法就是能否創(chuàng)建一個(gè)線程池,初始化到那里捅儒。當(dāng)我們需要使用的時(shí)候再拿來用就好了液样。

實(shí)際上 dart team 已經(jīng)為我們寫好一個(gè)非常實(shí)用的 package,其中就包括 LoadBalancer巧还。

我們現(xiàn)在 pubspec.yaml 中添加 isolate 的依賴鞭莽。

isolate: ^2.0.2
復(fù)制代碼

然后我們可以通過 LoadBalancer 創(chuàng)建出指定個(gè)數(shù)的 isolate。

Future<LoadBalancer> loadBalancer = LoadBalancer.create(2, IsolateRunner.spawn);
復(fù)制代碼

這段代碼將會(huì)創(chuàng)建出一個(gè) isolate 線程池麸祷,并自動(dòng)實(shí)現(xiàn)了負(fù)載均衡澎怒。

由于 dart 天生支持頂層函數(shù),我們可以在 dart 文件中直接創(chuàng)建這個(gè) LoadBalancer阶牍。下面我們再來看看應(yīng)該如何使用 LoadBalancer 中的 isolate喷面。

 int useLoadBalancer() async {
    final lb = await loadBalancer;
    int res = await lb.run<int, int>(_doSomething, 1);
    return res;
  }
復(fù)制代碼

我們關(guān)注的只有 Future<R> run<R, P>(FutureOr<R> function(P argument), argument, 方法。我們還是需要傳入一個(gè) function 在某個(gè) isolate 中運(yùn)行走孽,并傳入其參數(shù) argument惧辈。run 方法將會(huì)返回我們執(zhí)行方法的返回值。

整體和 compute 使用感覺上差不多磕瓷,但是當(dāng)我們多次使用額外的 isolate 的時(shí)候盒齿,不再需要重復(fù)創(chuàng)建了。

并且 LoadBalancer 還支持 runMultiple,可以讓一個(gè)方法在多線程中執(zhí)行县昂。具體使用請查看 api肮柜。

LoadBalancer 經(jīng)過測試,它會(huì)在第一次使用其 isolate 的時(shí)候初始化線程池倒彰。

image.png

當(dāng)應(yīng)用打開后审洞,即使我們在頂層函數(shù)中調(diào)用了 LoadBalancer.create,但是還是只會(huì)有一個(gè) Isolate待讳。

image.png

當(dāng)我們調(diào)用 run 方法時(shí)芒澜,才真正創(chuàng)建出了實(shí)際的 isolate。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末创淡,一起剝皮案震驚了整個(gè)濱河市痴晦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌琳彩,老刑警劉巖誊酌,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異露乏,居然都是意外死亡碧浊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門瘟仿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箱锐,“玉大人,你說我怎么就攤上這事劳较【灾梗” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵观蜗,是天一觀的道長臊恋。 經(jīng)常有香客問我,道長嫂便,這世上最難降的妖魔是什么捞镰? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮毙替,結(jié)果婚禮上岸售,老公的妹妹穿的比我還像新娘。我一直安慰自己厂画,他們只是感情好凸丸,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著袱院,像睡著了一般屎慢。 火紅的嫁衣襯著肌膚如雪瞭稼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天腻惠,我揣著相機(jī)與錄音环肘,去河邊找鬼。 笑死集灌,一個(gè)胖子當(dāng)著我的面吹牛悔雹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播欣喧,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼腌零,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了唆阿?” 一聲冷哼從身側(cè)響起益涧,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎驯鳖,沒想到半個(gè)月后闲询,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浅辙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年嘹裂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摔握。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丁寄,靈堂內(nèi)的尸體忽然破棺而出氨淌,到底是詐尸還是另有隱情,我是刑警寧澤伊磺,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布盛正,位于F島的核電站,受9級特大地震影響屑埋,放射性物質(zhì)發(fā)生泄漏豪筝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一摘能、第九天 我趴在偏房一處隱蔽的房頂上張望续崖。 院中可真熱鬧,春花似錦团搞、人聲如沸严望。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽像吻。三九已至峻黍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拨匆,已是汗流浹背姆涩。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惭每,地道東北人骨饿。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像洪鸭,于是被迫代替她去往敵國和親样刷。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345