Flutter线程模型

事件循环在UI框架里面应该算是一个常见的东西,例如安卓主线程里面就有个Looper一直在MessageQueue里面读取事件。Flutter里面也有类似的东西。

实际上Flutter的事件循环应该是Dart语言层面就支持的东西。Dart是单线程模型的编程语言,它的一个线程对应一个Isolate,而一个Isolate就会带有一个事件循环。值得注意的是虽然你可以启动多个Isolate来实现多线程,但是正如它的名字"隔离",Isolate之间是内存隔离的,它们有独立的堆内存,这意味着两个Isolate不会出现线程安全问题:

1.png

Isolate启动之后就会默认开启事件循环,并不需要我们干些什么。这里有个比较有意思的事情是,不像其他语言,main函数执行完成之后整个程序就结束并退出了。Dart的main函数执行完之后会进入事件循环,监听事件并且进行回调,这些事件包括按键事件、Timer事件等:

2.png

所以从这个角度看,Dart的main函数更像是一个init函数,它意味着Dart程序的启动,但它退出后Dart程序还会进行运行。例如下面的代码:

void main() {
  print("main begin");
  Timer(Duration(seconds: 3), () {
    print("on timer");
  });
  print("main begin finish");
}

通过打印我们可以看到,就算main函数退出了,等3秒后Timer时间到达依然会打印"on timer":

I/flutter (15551): main begin
I/flutter (15551): main begin finish
I/flutter (15551): on timer

消息队列

消息循环一般和消息队列配套使用,就例如安卓里面的Looper和MessageQueue。而Dart里面有两条消息队列microtask队列和event队列,时间循环会执行microtask队列内的任务,microtask队列为空之后再去执行event队列里面的任务:

3.png

event队列包含Dart和来自系统其它位置的事件。但microtask队列只包含来自当前isolate的内部代码。如果我们希望在这次事件循环处理完成之后,下一次事件循环处理之前做一些事情,就可以往microtask队列里面加入任务:

void main() {
  new Future(() {
    scheduleMicrotask(()=>print("in microtask queue 1"));
    print("in event queue 1");
  });
  new Future(() {
    scheduleMicrotask(()=>print("in microtask queue 2"));
    print("in event queue 2");
  });
  new Future(() {
    scheduleMicrotask(()=>print("in microtask queue 3"));
    print("in event queue 3");
  });
}

在main函数里面我们使用Future往event队列插入了3个task,但是执行到每个task的时候,又会往microtask队列插入microtask,这就造成task执行完之后重新遍历microtask队列发现microtask去执行:

in event queue 1
in microtask queue 1
in event queue 2
in microtask queue 2
in event queue 3
in microtask queue 3

Future有个then方法,可以在Future执行完之后执行指定操作:

void main() {
  new Future(() {
    scheduleMicrotask(()=>print("in microtask queue 1"));
    print("in event queue 1");
    return "a";
  }).then((a) {
    scheduleMicrotask(()=>print("in microtask queue 2"));
    print("in event queue 2 -> $a");
    return new Future(() { return "b";});
  }).then((b) {
    scheduleMicrotask(()=>print("in microtask queue 3"));
    print("in event queue 3 -> $b");
  });
}

一开始我理解错误,以为是在这个Future task后面插入另一个Future,其实实际上then里面的是callback,它的参数是上一个callback的返回值。callback会在Future执行完之后立即执行,而不会插入event队列。但如果上一个callback的返回值是Future的话,就会往event队列插入任务,而后面的callback实际上监听的是这个返回的Future。所以上面的代码打印如下:

I/flutter (23893): in event queue 1
I/flutter (23893): in event queue 2 -> a
I/flutter (23893): in microtask queue 1
I/flutter (23893): in microtask queue 2
I/flutter (23893): in event queue 3 -> b
I/flutter (23893): in microtask queue 3

单线程模型

线程阻塞

另外正如一开始说的Dart是单线程模型,由于这个事件循环是在单个线程内的,所以如果我们的task耗时比较长就会阻塞后面的task:

void main() {
  print('start time :' + DateTime.now().toString()); // 当前时间
  Timer(Duration(seconds: 1), () {
    //callback function
    print('first timer :' + DateTime.now().toString()); // 1s之后
    sleep(Duration(seconds: 3));
  });
  Timer(Duration(seconds: 2), () {
    //callback function
    print('second timer :' + DateTime.now().toString()); // 2s之后
  });
}

所以我们的第二个Timer虽然设定是在2秒后执行,但是实际上它会被第一个timer的3秒sleep阻塞住,等到第一个Timer结束才执行:

I/flutter (21196): start time :2021-10-20 21:30:11.794680
I/flutter (21196): first timer :2021-10-20 21:30:12.835643
I/flutter (21196): second timer :2021-10-20 21:30:15.841398

因此我们不能过分的信任这些定时任务。

未捕获的异常

不像java、kotlin这些语言多线程语言,如果一个线程中出现了未捕获的异常,那么这个线程就被强制结束了。由于采用事件循环的机制来运行相对独立的task,Dart不要求我们必须处理异常。当一个task出现了异常,虽然会结束这个task,但是并不影响整个线程,后续的其他task仍可以继续执行:

void main() {
  new Future(() {
    print("task1 begin");
    throw new Exception();
    print("task1 finish");
  });
  new Future(() {
    print("task2 begin");
    print("task2 finish");
  });
}

task1被中断了,但是task2依然会执行:

task1 begin
Error: Exception
    at Object.throw_ [as throw] (http://localhost:64924/dart_sdk.js:5041:11)
    at http://localhost:64924/packages/myflutter/main.dart.lib.js:365:17
    at http://localhost:64924/dart_sdk.js:32040:31
    at internalCallback (http://localhost:64924/dart_sdk.js:24253:11)
task2 begin
task2 finish

在Dart里面实现多线程

虽然通过Dart的async await可以实现类似kotlin的协程的功能,但是如果不做特殊操作,这些协程实际上是跑在同一个线程中的。一旦某个协程做了些耗时操作如复杂计算等,就会造成ui卡顿。这个时候我们可以创建多个Isolate去实现类似多线程的操作:

int globalData = 1;

void otherIsolate(SendPort sendPort) {
  while(true){
    globalData ++;
    sleep(Duration(seconds: 1));
    sendPort.send("globalData from otherIsolate $globalData");
  }
}

void main() {
  globalData = 100;

  ReceivePort receivePort = ReceivePort();
  receivePort.listen((message) {
    print(message);
  });

  Isolate.spawn(otherIsolate, receivePort.sendPort);
  Future.delayed(Duration(seconds: 3), ()=>{
    print("globalData in Future : $globalData")
  });
}

我们可以通过ReceivePort这种类似管道的东西来进行Isolate之间的通信。正如之前所说Isolate是内存隔离的,它更像一个进程的概念。所以并不能像其他语言的多线程一样通过全局变量来交换数据:

I/flutter (17503): globalData from otherIsolate 2
I/flutter (17503): globalData from otherIsolate 3
I/flutter (17503): globalData in Future : 100
I/flutter (17503): globalData from otherIsolate 4
I/flutter (17503): globalData from otherIsolate 5
I/flutter (17503): globalData from otherIsolate 6

参考

学习这部分知识的时候参考了下面的文章,有兴趣的同学可以看看:

给 Android 开发者的 Flutter 指南

Dart asynchronous programming: Isolates and event loops

Dart与消息循环机制[翻译]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容