IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Flutter —— 异步编程和多线程 -> 正文阅读

[移动开发]Flutter —— 异步编程和多线程

1. 异步编程

1.1 Future

Future 类,其表示一个 T 类型的异步操作结果。如果异步操作不需要结果,则类型为 Future。也就是说首先Future是个泛型类,可以指定类型。如果没有指定相应类型的话,则Future会在执行动态的推导类型。
在网络请求中经常使用异步编程,那么来探索一下Flutter 中的异步编程。

dart是单线程的,所以底层没有锁之类的东西,但是这不代表着dart不能异步,异步不代表多线程。
下面代码运行后发现做其他事情被堵塞住了,这里async不起作用是因为需要搭配Future使用。
在这里插入图片描述
将耗时操作使用Future包装起来,这里可以看到做其他事情就不会被堵塞住了,那么现在即使方法不加async也是异步的,因为Future里面已经是异步的了。或者说async不会异步执行,Future才会异步执行。
在这里插入图片描述
如果把 print(‘结束data = $_data’); 放在Future外面那么其就会先于Future里面的代码执行。
在这里插入图片描述

1.2 async ,await

那么如何让等待Future里面的执行完在执行后面代码呢?这时候需要用到await。加了await之后,后面的代码就会等待Future里面的执行完之后在执行。这里的await需要搭配async使用,而没有await的情况下,async是没有意义的。await后面的操作也需要是异步的。那么也就是说,Future是用来异步执行的,而async和await搭配是用来让Future里面的某块代码同步执行的。
在这里插入图片描述

1.3 Future.then()

Future的所有API的返回值仍然是一个Future对象,所以可以很方便的进行链式调用。这里可以使用then来将特定的需要等待的任务放进去,然后不堵塞后面的任务的执行。 如果then没有返回值的话,那么value就是null。
在这里插入图片描述
这里看到Future里面return的话那么value就是return的那个值。这个时候,Future里面返回的数据会被Future包装,然后给到了then里面 的value。
在这里插入图片描述

1.4 Future.catchError

异步中的错误是用catchError来进行处理的。当异步任务中出现异常之后,
如果调用下列方法就会报错,这是因为任务里面抛出了异常没有处理。

getData() async {
  print('开始data = $_data');
  //耗时操作
  Future future =  Future(() {
    for (int i = 0; i < 10000000; i++) {}
    throw Exception('网络异常');
    return "假的网络数据";
  });
  future.then((value) {
    print('value = $value');
  });

  print('做一点其他事情');
}

在.then 下面添加代码

  future.catchError((e){print("捕获到了错误:$e");}); 

运行后发现还是报错,但是捕获到了异常。正常来说如果处理了异常,就不应该报错了,那么这里是执行顺序的问题吗?
在这里插入图片描述
将catch和then交换位置后运行,发现还是报错
在这里插入图片描述
把then注释掉后发现不报错了。
在这里插入图片描述
那么这里要怎么处理呢?这里一般用链式调用。

getData() async {
  print('开始data = $_data');
  //耗时操作
  Future future = Future(() {
    for (int i = 0; i < 10000000; i++) {}
    throw Exception('网络异常');
    return "假的网络数据";
  }).then((value) {
    print('value = $value');
  }).catchError((e) {
    print("捕获到了错误:$e");
  });

  print('做一点其他事情');
}

或者在.then中添加onError对错误进行处理
在这里插入图片描述

 future.then((value) {
    print('value = $value');
  },onError: (error){print("捕获到了错误:$error");});

onError是在.then这一次对错误进行处理,而catchError则是多次链式调用中的错误处理
如果在.then之前catchError了,那么.then中的value就是catchError中传过来的值了。在catchError之前的.then不会执行。
在这里插入图片描述
在CatchError之后添加了whenComplete运行后发现还是报出了异常。

getData() async {
  print('开始data = $_data');
  //耗时操作
  Future future = Future(() {
    for (int i = 0; i < 10000000; i++) {}
    throw Exception('网络异常');
    return "假的网络数据";
  });

      future.catchError((e) {
    print('error = $e');
    return "错误";
  }).then((value) => print('$value'));

  future.whenComplete(() => print("完成了"));

  print('做一点其他事情');
}

这个时候可以在whenComplete之后添加一个catchError,或者使用链式调用。

    future.catchError((e) {
    print('error = $e');
    return "错误";
  }).then((value) => print('$value')).whenComplete(() => print("完成了"));

上面的例子可以看到链式调用能避免大部分的错误,所以在一般都是使用链式调用来进行处理的,并且把catchError放到最后调用,这样出现异常的时候,前面的.then就不会执行了。如果链式太长的话,可以创建方法来让链式更加简洁。

getData() async {
  print('开始data = $_data');
  //耗时操作
  Future future = Future(() {
    for (int i = 0; i < 10000000; i++) {}
    throw Exception('网络异常');
    return "假的网络数据";
  }).then(thenFunc).whenComplete(completeFunc).catchError((e) {
    print('error = $e');
    return "错误";
  });

  print('做一点其他事情');
}

FutureOr thenFunc(String value) {
}

FutureOr<void> completeFunc() {
}

1.5 多个Future

Future是放在队列中的,所以Future的执行是有顺序的。下面的代码结果输出是什么呢?

void main() {
  testFuture();
  print("A");
}

void testFuture() {
   Future((){
    sleep(Duration(seconds: 2));
    print("C");
  });

  print("B");
}

运行后看到是B->A->C;
在这里插入图片描述
那么下面的代码运行后会打印什么呢?

void main() {
  testFuture();
  print("A");
}

void testFuture() async {
  await Future((){
    sleep(Duration(seconds: 2));
    print("C");
  }).then((value) =>   print("D"));

  print("B");
}

运行后发现是A->C->D->B,这里因为B被C堵塞了,所以A会先执行,然后C执行完之后执行D,最后执行B。
在这里插入图片描述
那么下面代码的输出结果是什么呢?

void main() {
  testFuture();
  print("A");
}

void testFuture() async {
   Future((){
    return "任务1";
  }).then((value) =>   print("$value结束"));

   Future((){
     return "任务2";
   }).then((value) =>   print("$value结束"));

   Future((){
     return "任务3";
   }).then((value) =>   print("$value结束"));

   Future((){
     return "任务4";
   }).then((value) =>   print("$value结束"));

  print("任务添加完毕");
}

运行后发现是任务添加完毕->A->任务1结束->任务2结束->任务3结束->任务4结束。
在这里插入图片描述

那么这里任务一定是按顺序执行的吗?在任务二添加sleep后重新执行,发现任务顺序还是一样的。
在这里插入图片描述
这说明这里会按异步任务的添加顺序执行的。

1.6 Future.wait

当需要等待多个Future完成,并收集它们的结果,可以使用Future.wait。这个时候.then就会等wait里面所有任务完成后在执行,然后返回的值可以用数组来取。wait里面的任务同时处理,但是是按添加顺序执行的,而如果是链式执行的话,则是一个执行完在执行下一个。

 Future.wait([
  Future((){
    return "任务1";
  }),
  Future((){
  return "任务2";
  }),
  Future((){
  return "任务3";
  }),
  Future((){
  return "任务4";
  }),
  ]).then((value) => print(value[0] + value[1] + value[2]   ));

1.7 microtask

Dart的事件循环(event loop)

在Dart中,实际上有两种队列:

  • 事件队列(event queue),包含所有的外来事件:I/O、mouse events、drawing events、timers、isolate之间的信息传递。
  • 微任务队列(microtask queue),表示一个短时间内就会完成的异步任务。它的优先级最高,高于event queue,只要队列中还有任务,就可以一直霸占着事件循环。microtask queue添加的任务主要是由 Dart内部产生。

因为microtask queue 的优先级高于 event queue ,所以如果 microtask queue有太多的微任务, 那么就可能会霸占住当前的event loop。从而对event queue中的触摸、绘制等外部事件造成阻塞卡顿。

在每一次事件循环中,Dart总是先去第一个microtask queue中查询是否有可执行的任务,如果没有,才会处理后续的event queue的流程。
在这里插入图片描述

异步任务我们用的最多的还是优先级更低的 event queue。Dart为 event queue 的任务建立提供了一层封装,就是我们在Dart中经常用到的Future。

正常情况下,一个 Future 异步任务的执行是相对简单的:

声明一个 Future 时,Dart 会将异步任务的函数执行体放入event queue,然后立即返回,后续的代码继续同步执行。
当同步执行的代码执行完毕后,event queue会按照加入event queue的顺序(即声明顺序),依次取出事件,最后同步执行 Future 的函数体及后续的操作。
上面说过了microtask队列的优先级比较高,那么使用microtask就可以让任务优先执行。
下面代码的先执行代码1,代码2,A以及B。

void testFuture3() {
  print('外部代码1');
  Future(()=>print('A')).then((value) => print('A结束'));
  Future(()=>print('B')).then((value) => print('B结束'));
  print('外部代码2');
}

添加了微代码后,那么微任务就会在异步任务之前执行。

void testFuture3() {
  print('外部代码1');

  Future(()=>print('A')).then((value) => print('A结束'));
  Future(()=>print('B')).then((value) => print('B结束'));
  scheduleMicrotask(() {
    print("微任务A");
  });
  print('外部代码2');
}

下面的任务执行后打印情况是什么样的呢?这里5一定是先执行的,然后执行微任务3,然后异步任务按照添加的顺序执行,那么就会先执行future1打印1和4,最后执行future2 打印2,所以打印 5 —— 3 —— 1 —— 4 —— 2;

void testFuture4() {
 Future future1 =  Future((){print('1');});
 Future future2 =  Future((){print('2');});
 scheduleMicrotask(() {
   print("微任务3");
 });
 future1.then((value) => print('4'));
 print('5');
}

运行后验证果真是的。
在这里插入图片描述
那么这里的打印顺序是什么呢?这里future3最先被添加到队列,所以依然会比 1, 2 优先执行,所以会先打印6,所以打印 5 —— 3 —— 6 —— 1 —— 4 —— 2;
在这里插入图片描述
打印结果:
在这里插入图片描述
那么如果是这样的话,打印结果会是什么呢?按照图片里的,执行future3的时候会把自身任务执行完再重新开始循环,那么也就是说,这里5,3之后会先打印6,8 ,然后再打印7,然后再打印142。

void testFuture4() {
  Future future3 = Future(() => null);
  future3.then((value) {
    print('6');
    scheduleMicrotask(() {
      print("7");
    });
  }).then((value) =>  print("8"));

  Future future1 = Future(() {
    print('1');
  });

  future1.then((value) => print('4'));
  Future future2 = Future(() {
    print('2');
  });
  scheduleMicrotask(() {
    print("3");
  });

  print('5');
}

打印结果:
在这里插入图片描述
那么把.then拆出来的话是什么结果呢?其实这里相当于把.then里面的任务放到微任务里面去了,所以8依然会优先执行。这也是为什么 future1里面的then的任务会比 future2先执行。

  Future future4 =  future3.then((value) {
    print('6');
    scheduleMicrotask(() {
      print("7");
    });
  });
  
  future4.then((value) => print("8"));

打印结果:
在这里插入图片描述

2. 多线程

Flutter默认是单线程任务处理的,如果不开启新的线程,任务默认在主线程中处理。和iOS应用很像,在Dart的线程中也存在事件循环和消息队列的概念,但在Dart中线程叫做isolate。应用程序启动后,开始执行main函数并运行main isolate。

下面的打印结果一定是123,因为这里Future会等待主线程睡眠结束。

void IsolateDemo() {
  print('1');
  Future(func);
  sleep(Duration(seconds: 2));
  print('2');
}

FutureOr func() {
  print('3');
}

那么如果将func放在Isolate里面执行,那么在打印2之前func就会先打印3。

void IsolateDemo() {
  print('1');

  Isolate.spawn(func,10);
  sleep(Duration(seconds: 1));
  print('2');
}


void func(int message) {
  print('3');
}

在这里插入图片描述
多添加几次任务来执行。

void IsolateDemo() {
  print('外部代码1');

  Isolate.spawn(func,10);
  Isolate.spawn(func2,10);
  Isolate.spawn(func,10);
  Isolate.spawn(func2,10);
  Isolate.spawn(func,10);
  Isolate.spawn(func2,10);
  sleep(Duration(seconds: 1));
  print('外部代码2');
}

运行后发现这里任务一不一定比任务2先执行,而且外部代码也会在任务一任务二之前执行,证明了这里Isolate确实是子线程里面执行任务的。
在这里插入图片描述
Dart中的isolate更像是一个进程,因为isolate有独立的内存空间,意味着每个isolate中的数据是独立的,所以没有资源抢夺的问题,也就不需要锁。所以,我们访问数据不能直接访问。
声明一个属性没然后在func里面赋值,在IsolateDemo里面睡眠之后打印a的值看一下是否改变。

int a = 10;
void IsolateDemo() {
  print('外部代码1');

  Isolate.spawn(func,1000);

  sleep(Duration(seconds: 1));
  print('外部代码来了a=$a');
  print('外部代码2');
}

void func(int message) {
  a = message;
  print('第一个来了a=$a');
}

运行后发现是没有改变的
在这里插入图片描述
那么如果非要改变a的值的话,那么就需要用到port,将port的sendPort作为参数传给func,然后添加listen监听数据变化来接受func里面传过来的值。

void IsolateDemo() {
  print('外部代码1');
  //  创建一个port
  ReceivePort port = ReceivePort();
// 创建一个Isolate
  Isolate.spawn(func,port.sendPort);
  //监听数据变化
  port.listen((message) {
    a = message;
    print('接受到了a=$a');
  });

  sleep(Duration(seconds: 1));
  print('外部代码来了a=$a');
  print('外部代码2');
}


void func(SendPort send) {
  send.send(100);
  print('第一个来了a=$a');
}

运行后发现a的值改变了。
在这里插入图片描述
既然我们Isolate开辟了空间,那么我们就要手动去销毁Isolate。 这里创建临时变量iso,然后在port.listen里面关闭端口以及杀掉iso。这里的await不会堵塞后面代码的执行,因为这里是其他线程里面的。

void IsolateDemo() async {
  print('外部代码1');
  //  创建一个port
  ReceivePort port = ReceivePort();
  // 创建一个Isolate
 Isolate iso =  await Isolate.spawn(func,port.sendPort);

  //监听数据变化
  port.listen((message) {
    a = message;
    print('接受到了a=$a');
    port.close();
    iso.kill();
  });
  print('外部代码来了a=$a');
  print('外部代码2');
}

关于Iso还有一个封装叫compute,这里使用compute之后发现这里并不会堵塞住compute的执行。

void computeTest() {
  print('外部代码1');
  compute(func1,10);
  sleep(Duration(seconds: 2));
  print('外部代码2');
}
FutureOr func1(message) {
  print('compute');
}

运行结果:
在这里插入图片描述
compute和iso不一样的是,如果加了await,那么后面的代码就需要等待。并且compute可以返回数据来修改数据的值,但是如果在func1修改a的值的话同样是没有效果的。

void computeTest() async{
  print('外部代码1');
  a = await compute(func1,10);
  print('外部代码2 a = $a');
}
int func1(message) {
  sleep(Duration(seconds: 2));

  print('compute');
  return 1000;
}

在这里插入图片描述

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-12 19:42:39  更:2021-11-12 19:43:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:08:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码