网络多线程

Future的错误和状态

让异步耗时操作处理完成之后,再执行下面代码,Future前面需要添加await

String _data = '0';

void main() {
  getData();
  print('做其他事情');
}

getData() async {
  print('开始data=$_data');
  //耗时操作
  //1.后面的操作必须是异步才能使用await修饰
  //2.当前这个函数也必须是async修饰的函数!
  await Future(() {
    for (int i = 0; i < 1000000000; i++) {}
    return '网络数据';
  }).then((value) => print('value:$value'));//使用Future,相当于把里面的代码块,丢进异步任务里面去!!

  // async与await配合使用,主要是让上面异步任务执行完成再执行这里,Future前面需要添加await
  print('再多干点事情!!');
}
查看日志

下面我们来处理网络请求过程中,可能出现的异常状态

  • 网络请求抛出异常状态
String _data = '0';

void main() {
  getData();
  print('做其他事情');
}

getData() async {
  print('开始data=$_data');
  //耗时操作
  //1.后面的操作必须是异步才能使用await修饰
  //2.当前这个函数也必须是async修饰的函数!
  Future future = Future(() {
    for (int i = 0; i < 1000000000; i++) {}
    throw Exception('网络异常');
  });

  future.then((value) => print('value:$value'));//使用Future,相当于把里面的代码块,丢进异步任务里面去!!

  // 上面异步任务执行完成再执行这里,Future前面需要添加await
  // async与await配合使用,主要对下面代码执行有效
  print('再多干点事情!!');
}
抛出异常
  • 网络请求捕获异常
String _data = '0';

void main() {
  getData();
  print('做其他事情');
}

getData() async {
  print('开始data=$_data');
  //耗时操作
  //1.后面的操作必须是异步才能使用await修饰
  //2.当前这个函数也必须是async修饰的函数!
  Future future = Future(() {
    for (int i = 0; i < 1000000000; i++) {}
    throw Exception('网络异常');
  });

  future.then((value) => print('value:$value'));//使用Future,相当于把里面的代码块,丢进异步任务里面去!!
  future.catchError((e) => print('捕获到了:'+e.toString()));
  // 上面异步任务执行完成再执行这里,Future前面需要添加await
  // async与await配合使用,主要对下面代码执行有效
  print('再多干点事情!!');
}
查看日志

通过日志可以看出,我们已经捕获到了异常,程序依然发生Exception错误,该怎么解决呢?

  • 方案一:我们需要使用链式写法,来避免捕获异常程序报错的情况。
future.then((value) => print('value:$value'))
        .catchError((e) => print('捕获到了:'+e.toString()));
  • 方案二:使用.then里面的onError
future.then((value) => print('value:$value'),
        onError: (e) => print(e.toString()));
查看日志
onErrorcatchError的区别?

onError只在当前这一次.then回调里面有效,而catchError是对整个future链式内有效。
现在把. catchError. then顺序交换一下

future.catchError((e) => print('捕获到了:'+e.toString()))
      .then((value) => print('value:$value'));
查看日志

. catchError已经捕获了异常,.then的回调依然执行了。

  • 网络请求完成状态
future.then((value) => print('value:$value'))
      .catchError((e) => print('使用的时候捕获到了:'+e.toString()));

future.whenComplete(() => print('完成了'))
      .catchError((e) => print('完成的时候捕获到了:'+e.toString()));
查看日志

.then使用的时候,. whenComplete完成的时候都可以捕获异常

使用简便的写法,注意.catchError要写在最后面。

  • 写法一
getData() async {
  print('开始data=$_data');
  //耗时操作
  //1.后面的操作必须是异步才能使用await修饰
  //2.当前这个函数也必须是async修饰的函数!
  Future(() {
    for (int i = 0; i < 1000000000; i++) {}
    throw Exception('网络异常');
  }).then((value) => print('value:$value'))
    .whenComplete(() => print('完成了'))
    .catchError((e) => print('捕获到了:'+e.toString()));

  print('再多干点事情!!');
}
查看日志
  • 写法二,这种写法. catchError写在前面并不会报错,但是最好把. catchError放在后面,防止程序出现一些不必要的报错。
getData() async {
  print('开始data=$_data');
  //耗时操作
  //1.后面的操作必须是异步才能使用await修饰
  //2.当前这个函数也必须是async修饰的函数!
  Future future = Future(() {
    for (int i = 0; i < 1000000000; i++) {}
    throw Exception('网络异常');
  });

  future.catchError((e) => print('捕获到了:'+e.toString()))
      .then((value) => print('value:$value'))
      .whenComplete(() => print('完成了'));

  print('再多干点事情!!');
}
查看日志

注意:网络请求最好使用链式写法,如果网络请求回调里面代码量大的话,完全可以把回调里面的处理抽成方法

多个异步处理

多线程与异步的区别:多线程的执行顺序是随机的,异步的执行是有序的

如果一个方法里面有多个Future异步任务,那么执行顺序是什么样呢?

  • 案例一
void main() {
  testFuture();
  print('A');
}

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

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

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

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

  print('任务添加完毕');
}
查看日志
  • 案例二:任务二休眠一秒钟,查看执行顺序
import 'dart:io';

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

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

  Future(() {
    sleep(Duration(seconds: 1));
    return '任务二';
  }).then((value) => print('$value结束'));

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

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

  print('任务添加完毕');
}
查看日志

由上面两个案例可以看出,任务队列的执行是有序的,根据异步代码的添加顺序来执行。

如果任务的执行有依赖关系,该怎么解决?

  • 案例三:任务二依赖于任务一的结果等等,使用.then嵌套处理
import 'dart:io';

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

void testFuture() {
  Future(() {
    sleep(Duration(seconds: 1));
    return '任务一';
  }).then((value) {
    print('$value结束');
    return '$value任务二';
  }).then((value) {
    print('$value结束');
    return '$value任务三';
  }).then((value) {
    print('$value结束');
    return '$value任务四';
  });

  print('任务添加完毕');
}
查看日志

如果有多个任务,都要执行结束再,该怎么解决?

  • 案例四
import 'dart:io';

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

void testFuture() {
  Future.wait([
    Future(() {
      return '任务一';
    }),
    Future(() {
      return '任务二';
    }),
    Future(() {
      return '任务三';
    }),
  ]).then((value) => print(value[0] + value[1] + value[2]));
}
查看日志
  • 案例五:任务二休眠一秒钟,查看执行顺序
import 'dart:io';

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

void testFuture() {
  Future.wait([
    Future(() {
      print('任务一 执行');
      return '任务一';
    }),
    Future(() {
      sleep(Duration(seconds: 1));
      print('任务二 执行');
      return '任务二';
    }),
    Future(() {
      print('任务三 执行');
      return '任务三';
    }),
  ]).then((value) => print(value[1] + value[0] + value[2]));
}
查看日志

里面的三个任务依然是一个串行队列

Dart事件循环

上面我们在执行任务的时候都是串行队列,现在有一个比较紧急的任务想提前执行,该怎么解决呢?

  • 案例一:使用scheduleMicrotask微任务,优先级更高;微任务的优先级比事件队列高。
import 'dart:async';
import 'dart:io';

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

void testFuture() {
  print('外部代码一');

  Future(() => print('A')).then((value) => print('A结束'));
  Future(() => print('B')).then((value) => print('B结束'));

  scheduleMicrotask(() {
    print('微任务A');
  });
  sleep(Duration(seconds: 2));
  print('外部代码二');
}
查看日志

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

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

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

推荐学习文章:Dart的事件循环

如果有多个微任务执行,顺序又是什么呢?

  • 案例二:多个微任务执行
void testFuture() {
  // x任务开始
  Future x = Future(() => print('1'));
  Future(() => print('2'));
  scheduleMicrotask(() => print('3'));
  // x任务处理
  x.then((value) => print('4'));
  print('5');
}
查看日志

首先执行主线程任务打印5,再执行微任务打印3,最后执行同步队列打印 1 4 2

  • 案例三
void testFuture() {
  Future x1 = Future(() => null);
  Future x = Future(() => print('1'));
  Future(() => print('2'));
  scheduleMicrotask(() => print('3'));
  x.then((value) => print('4'));
  print('5');
  x1.then((value) => print('6'));
}
查看日志

首先执行主线程任务打印5,再执行微任务打印3,最后执行同步队列打印 6 1 4 2

  • 案例四
void testFuture() {
  Future x1 = Future(() => null);
  x1.then((value) {
    print('6');
    scheduleMicrotask(() => print('7'));
  }).then((value) => print('8'));

  Future x = Future(() => print('1'));
  x.then((value) => print('4'));

  Future(() => print('2'));

  scheduleMicrotask(() => print('3'));
  print('5');
}
查看日志

首先执行主线程任务打印5,再执行微任务打印3,再执行同步队列任务,由于x1任务中.then回调里面的代码,就是放在微任务里面,所以先执行8,再执行7,同步队列打印顺序是6 8 7 1 4 2

  • 案例五
void testFuture() {
  Future x1 = Future(() => null);
  Future x2 = x1.then((value) {
    print('6');
    scheduleMicrotask(() => print('7'));
  });
  // .then里面的代码块就是放在微任务里面执行
  x2.then((value) => print('8'));

  Future x = Future(() => print('1'));
  x.then((value) => print('4'));

  Future(() => print('2'));

  scheduleMicrotask(() => print('3'));
  print('5');
}
查看日志 -> 打印相同
  • 案例六
void testFuture() {
  Future x1 = Future(() => null);
  Future x2 = x1.then((value) {
    print('6');
    scheduleMicrotask(() => print('7'));
  });
  // .then里面的代码块就是放在微任务里面执行
  x2.then((value) => print('8'));

  Future x = Future(() => print('1'));
  x.then((value) {
    print('4');
    Future(() => print('9'));
  }).then((value) => print('10'));

  Future(() => print('2'));

  scheduleMicrotask(() => print('3'));
  print('5');
}
查看日志

x任务执行到.then才会添加任务9,说明任务9任务2之后,所以先打印2,最终打印结果是5 3 6 8 7 1 4 10 2 9

Dart中的多线程Isolate

上面我们学习的Dart都是在主线程中操作,也就意味着执行的顺序都是同步执行的;Future只是异步执行,并没有开启子线程,依然是在主线程中执行的。

  • 案例一
import 'dart:async';
import 'dart:io';

void main() {
  IsolateDemo();
}

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

func() => print('3');
查看日志

主线程任务卡住了,任务3得等待主线程执行结束,才能执行;下面我们来学习Flutter中多线程的用法,来解决主线程卡住不能执行异步任务的问题,关键词Isolate

import 'dart:async';
import 'dart:io';
// 导入头文件
import 'dart:isolate';

void main() {
  IsolateDemo();
}

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

func(int count) => print('3');
查看日志

成功解决,主线程卡住了,不用等待主线程执行结束再去执行异步任务

  • 案例二:Dart中的多线程
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: 2));
  print('外部代码2');
}

func(int count) {
  print('第一个来了$count');
}
func2(int count) {
  print('第二个来了$count');
}
查看日志

可以看出任务的执行是随机的,这就是Dart中的多线程;Isolate有独立的内存空间,更像是进程,每个进程之间的数据是独立的,也就意味着不存在资源抢夺的问题,也就不需要锁Dart中的多线程不需要锁,这样用起来就非常便捷。

  • 案例三:验证Dart多线程中数据的独立性
int a = 10;

void IsolateDemo() {
  print('外部代码1');
  Isolate.spawn(func,100);
  sleep(Duration(seconds: 2));
  print('a = $a');
  print('外部代码2');
}

func(int count) {
  a = count;
  print('第一个来了: a = $a');
}
查看日志

子线程中已经把a的值更改了,主线程中的a = 10,说明主线程中的数据与子线程中的数据是独立的;成功验证了数据独立性

  • 案例四:如果子线程中更改了a的值,想让主线程的值也更改,可以通过listen监听更改。
int a = 10;

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

func(SendPort send) {
  send.send(100);
  print('第一个来了: a = $a');
}
查看日志
  • 案例五:Isolate开辟了一个独立的空间,就需要手动去关闭该空间;关键词kill
int a = 10;

void IsolateDemo() async {
  print('外部代码1');
  // 创建一个port
  ReceivePort port = ReceivePort();
  // 创建Isolate
  // 这里使用了await, 方法后面就需要添加async
  Isolate iso = await Isolate.spawn(func, port.sendPort);
  // 监听数据变化
  port.listen((message) {
    a = message;
    print('接收到了$a');
    // 关闭端口port
    port.close();
    // 杀掉Isolate子线程
    iso.kill();
  });
  sleep(Duration(seconds: 2));
  // Isolate添加await并不会阻塞这里的代码执行
  print('a = $a');
  print('外部代码2');
}

func(SendPort send) {
  sleep(Duration(seconds: 1));
  send.send(100);
  print('第一个来了: a = $a');
}
查看日志

Dart中多线程除了有Isolate,还有一个是基于Isolate的封装compute

  • 案例六:多线程compute的使用
import 'dart:async';
import 'dart:io';
import 'dart:isolate';
// 导入头文件
import 'package:flutter/foundation.dart';

void main() {
  computeDemo();
}

void computeDemo() {
  print('外部代码1');
  compute(func2, 10);
  sleep(Duration(seconds: 2));
  print('外部代码2');
}

func2(int count) {
  print('第二个来了$count');
}
查看日志
  • 案例七:compute可以接收方法返回值,同时可以用来更改主线程的变量值
int a = 10;

void computeDemo() async {
  print('外部代码1');
  a = await compute(func2, 10);
  print('外部代码2: a = $a');
}

int func2(int count) {
  sleep(Duration(seconds: 2));
  print('第二个来了');
  return 10000;
}
查看日志
  • 案例八:compute方法内部并不能更改主线程变量值
int a = 10;

void computeDemo() async {
  print('外部代码1');
  int b = await compute(func2, 10);
  print('外部代码2: a = $a b = $b');
}

int func2(int count) {
  a = 999;
  sleep(Duration(seconds: 2));
  print('第二个来了');
  return 10000;
}
查看日志

compute只是对Isolate做了一层封装,线程间的通信本质还是用到listen传值监听;compute不需要手动kill关闭,但是创建端口port还是要手动close

pubspec.yaml文件管理

前面我们在进行网络请求的时候,使用的是官方http,还有一个网络请求框架dio;dio是一个强大的Dart Http请求库,支持Restful APIFormData拦截器请求取消Cookie管理文件上传/下载超时自定义适配器等...

dio网络请求库 - 用法学习

我们在使用http网络请求的时候,导入头文件import 'package:http/http.dart' as http;

/*  关于import
 *  1、as关键字 -- 给库起别名! 目的:防止类名方法名冲突!
 *  2、导入库,默认是整个文件中的都会导入,下面是两个关键字
 *     * show:执行需要导入的内容
 *     * hide:需要隐藏的内容。
 * */
头文件导入配置
pubspec.yaml文件介绍
# 项目名称,必填字段
name: future_demo
# 项目描述,非必填字段
description: A new Flutter project.

# 指定包发布的位置 none表示不发布,默认发布在 https://pub.dev/
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# 当前flutter工程版本
version: 1.0.0+1

# dart sdk指定兼容版本
environment:
  sdk: ">=2.12.0 <3.0.0"

# 三方flutter sdk 默认指定最新版本
dependencies:
  flutter:
    sdk: flutter
    # 也可以指定flutter sdk版本
    version: ***

  # 开发环境的依赖库
  cupertino_icons: ^1.0.2
  dio: ^4.0.1 # ^表示大版本不变的区间写法。相当于'>=4.0.1 < 5.0.0',引用这个版本区间的最新版本
  # dio: 4.0.1 指定4.0.1
  # dio: any 任意版本,默认是最新版本
  # dio: '>3.0.1' 大于3.0.1(不包含),配置大于小于的话要添加字符串

  # 开发打包,开发环境依赖的一些库版本,调试的时候使用
dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^1.0.0

# The following section is specific to Flutter.
# 项目中配置的一些声明方式,主要涉及flutter项目的资源
flutter:

  uses-material-design: true

  # 资源image
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg

  # example:
  # 配置字体
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

注意: 配置文件中缩进的对齐

pubspec.yaml文件配置完成之后,上面会有提示

提示 Pub get配置生效
Pub get生效之后,左侧可以看到引入的库
Dio库的使用
// 导入头文件
import 'package:dio/dio.dart';

void main() {
  dioDemo();
}

void dioDemo() {
  //发送网络请求
  final dio = Dio();
  dio.get('https://www.baidu.com').then((value) => print(value));
}
成功打印百度html文件

你可能感兴趣的:(网络多线程)