文章目录
- Dart是不是单线程模型?是如何运行的?
- dart是值传递还是引用传递?
- mixin extends implement之间的关系。
- Dart中如何取消正在执行中的异步任务?
- Dart中Future和Stream区别?
Dart是不是单线程模型?是如何运行的?
一句话总结
- Dart 是单线程模型,它的核心是事件循环机制,通过微任务队列和事件队列来高效地处理异步操作,从而保证了UI线程的流畅性。
核心概念
Dart是单线程模型,不必像在Java或C#中那样担心共享内存的锁竞争和线程同步问题。运行机制可以从以下三个层面来理解:
1. 核心机制:事件循环 (Event Loop)
Dart 的事件循环依赖两个有序队列,按优先级执行:
- 微任务队列 (Microtask Queue) :优先级最高;通过
scheduleMicrotask()显式添加,附着在这个 Future 上的回调(包括then、catchError、whenComplete以及await的续延代码)都会被调度到微任务队列中执行。 - 事件队列 (Event Queue) :我们日常使用的绝大部分异步操作都来源于这里,例如
I/O操作(文件、网络请求)、手势识别、绘图消息、Timer(Future.delayed)。
事件循环的工作流程图:
flowchart TD
A[事件循环启动] --> B{微任务队列<br>是否为空?}
B -- 否 --> C[取出并执行<br>一个微任务]
C --> B
B -- 是 --> D{事件队列<br>是否为空?}
D -- 否 --> E[取出并执行<br>一个事件]
E --> F[事件执行完毕]
F --> B
D -- 是 --> G[等待新事件到来]
G --> B
事件循环代码演示:
void main() {
// ------------------------------
// 1. 同步代码(优先执行,事件循环启动前)
// ------------------------------
print("1. 同步代码 - 开始执行");
// ------------------------------
// 2. 微任务队列(Microtask Queue):优先级高于事件队列
// 通过 Future.microtask 添加
// ------------------------------
Future.microtask(() {
print("2. 微任务A - 微任务队列");
});
// ------------------------------
// 3. 事件队列(Event Queue):优先级低于微任务队列
// 通过 Future 或 Future.delayed 添加(即使延迟0秒)
// ------------------------------
// 事件1:执行时会新增微任务
Future(() {
print("4. 事件1 - 事件队列(开始执行)");
// 在事件执行过程中新增微任务
Future.microtask(() {
print("5. 微任务B(事件1中新增) - 微任务队列");
});
print("6. 事件1 - 事件队列(执行结束)");
});
// 再添加一个微任务(微任务队列按添加顺序执行)
Future.microtask(() {
print("3. 微任务C - 微任务队列");
});
// 事件2:在事件1之后执行
Future.delayed(Duration.zero, () {
print("7. 事件2 - 事件队列");
});
// ------------------------------
// 1. 同步代码(继续执行)
// ------------------------------
print("8. 同步代码 - 执行结束(事件循环即将启动)");
}
执行结果:
1. 同步代码 - 开始执行
8. 同步代码 - 执行结束(事件循环即将启动)
2. 微任务A - 微任务队列
3. 微任务C - 微任务队列
4. 事件1 - 事件队列(开始执行)
6. 事件1 - 事件队列(执行结束)
5. 微任务B(事件1中新增) - 微任务队列
7. 事件2 - 事件队列
步骤拆解(对应执行结果):
- 同步代码优先执行:
打印1和8(同步代码不进入任何队列,事件循环启动前就执行完毕)。 - 事件循环启动,先清空微任务队列:
按添加顺序执行微任务 A 和 C,打印2和3(微任务队列此时为空)。 - 处理事件队列的第一个事件(事件 1) :
- 执行事件 1 的代码,打印
4和6。 - 事件 1 执行过程中新增微任务 B(加入微任务队列)。
- 执行事件 1 的代码,打印
- 事件 1 执行完毕后,再次清空微任务队列:
执行新增的微任务 B,打印5(微任务队列再次为空)。 - 处理事件队列的下一个事件(事件 2) :
执行事件 2,打印7(事件队列此时为空)。 - 所有队列清空,程序结束。
2. 实现异步的关键:Future 和 async/await
- Future:它不是一个并行计算的结果,而是一个 “承诺” ,承诺在未来某个事件循环轮次中给你一个值(或一个错误)。当你调用
http.get()或Future.delayed()时,Dart会立刻返回一个Future对象,并立即返回(不会阻塞),I/O操作(网络/文件)由操作系统异步处理(Dart线程之外),主线程(Dart线程)仅处理回调。 - async/await:这只是编写异步代码的 “语法糖” ,让异步代码看起来和同步代码一样直观。在函数前加上
async关键字,其返回值会被自动包装为Future。await关键字的作用是:告诉事件循环“我这边有个Future需要一些时间才能完成,你先去处理其它事件(微任务或UI事件),等这个Future有结果了再回来继续执行我后面的代码。” 这非常重要,它不会阻塞线程,而是让出执行权。
代码演示:
// 需安装http库 flutter pub add http
import 'package:http/http.dart' as http;
void loadData() async {
print('1: 开始请求');
var data = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
); // 不会阻塞,只是“等待”
print('3: 收到数据: $data');
}
void main() {
loadData();
print('2: 执行其它操作');
}
执行结果:
1: 开始请求
2: 执行其它操作
3: 收到数据: Instance of 'Response'
梳理流程:
-
发起请求(同步) :
http.get()被调用。- 它同步地创建一个
Future对象并返回。 - 它同步地发起一个系统调用(比如用
libcurl或系统的 HTTP 库),将这个网络 I/O 操作委托给操作系统在 Dart 线程之外执行。此时,Dart 线程本身是空闲的,可以继续执行事件循环。
-
遇到
await(同步 -> 异步) :await关键字检查它右边的Future。如果这个 Future 尚未完成(http.get返回的肯定未完成),它会暂停当前函数的执行。- 它向这个未完成的 Future 订阅一个回调。这个回调包含了
await之后的所有代码(即print('3: ...'))。 - 这个回调的调度机制,与
future.then(...)的回调调度机制是完全相同的。
-
等待与完成(在Dart线程外) :
- Dart 事件循环继续运行,处理微任务和UI事件(比如执行
print('2'))。 - 操作系统在后台处理网络请求。
- Dart 事件循环继续运行,处理微任务和UI事件(比如执行
-
请求完成,通知Dart(事件队列) :
- 当操作系统完成网络请求后,它会通过一种机制(如
epoll、kqueue或IOCP)通知 Dart 运行时。 - 这个“通知”作为一个事件,被放入 Dart 的
事件队列(Event Queue)中。 这个事件本身不包含数据处理逻辑,只是一个信号。
- 当操作系统完成网络请求后,它会通过一种机制(如
-
事件循环处理完成事件(从事件队列到微任务队列) :
- 事件循环从事件队列中取出这个“网络请求完成”的事件。
- 处理这个事件的代码(Dart 运行时内部代码)会完成之前
http.get返回的那个 Future 对象(即调用_completer.complete(response))。 - 最关键的一步来了:当
future.complete()被调用时,Dart 的运行时会 schedule 一个微任务。这个微任务的任务就是:执行所有通过then、catchError或await注册在这个 Future 上的回调函数。
-
执行回调(微任务队列) :
- 当事件循环处理完当前的事件后,它会检查微任务队列。
- 它发现了第5步中被调度的微任务,于是执行它。
- 这个微任务的内容就是执行
print('3: 收到数据: $data')。
操作系统完成I/O -> 发送信号至 Dart 事件队列 -> 事件循环处理该信号 -> Future.complete()被调用 -> 调度一个微任务来执行所有回调 -> 事件循环处理微任务 -> 执行 await 之后的代码。
3. 处理真正耗时计算:Isolate
事件循环和异步处理对于I/O操作是完美的,但对于CPU密集型计算(如图像处理、加密解密、复杂JSON解析),如果计算本身就在Dart线程上运行,它就会阻塞事件循环,导致队列中的所有其他任务(包括UI渲染)都无法处理,应用就会卡顿。
Dart的解决方案是 Isolate。
- 不共享内存:每个Isolate都有自己的独立内存堆(Heap)和事件循环。Isolate之间不共享任何状态,通信的唯一方式是通过消息传递(Passing Messages) 。
- 真正的并行:由于现代设备是多核CPU,不同的Isolate可以被调度到不同的CPU核心上真正地并行运行。
- 通信成本:因为不共享内存,Isolate之间传递消息时,数据会发生拷贝。虽然对于简单数据很快,但对于大型数据(如图片、大列表),这个拷贝成本会很高。通常使用
SendPort和ReceivePort进行通信。
在Flutter中,你可以使用Isolate.spawn()或更高级的compute()函数来将繁重任务抛到新的Isolate中执行。
代码演示:
import 'package:flutter/foundation.dart';
// 使用 compute (Flutter提供的简便API)
void main() async {
var result = await compute(heavyTask, 1000000000);
print(result);
}
// 这个函数会在新的Isolate中执行
int heavyTask(int count) {
int sum = 0;
for (int i = 0; i < count; i++) {
sum += i;
}
return sum;
}
dart是值传递还是引用传递?
一句话总结
- Dart 是严格的值传递语言,所有参数传递的都是 “值的副本”。
核心概念
Dart 中的数据类型分为基本类型(Primitive Types) 和对象类型(Object Types) ,两者在值传递时的表现不同,容易造成 “引用传递” 的误解:
1. 基本类型(数值、布尔、字符串、null)
基本类型的值传递是直接传递值的副本,函数内部修改参数不会影响外部变量。
代码演示:
void modifyInt(int a) {
a = 100; // 修改的是副本
print("函数内:$a"); // 输出:100
}
void main() {
int x = 10;
modifyInt(x); // 传递x的副本(值为10)
print("函数外:$x"); // 输出:10(原始值未变)
}
- 解析:
x的值10被复制一份传递给a,函数内修改a不会影响x,符合值传递特性。
2. 对象类型(类实例、列表、映射等)
对象类型的值传递是传递对象引用的副本(即内存地址的副本)。这意味着:
- 函数内部和外部的变量持有同一个对象的不同引用副本(但指向同一块内存)。
- 若修改对象的内部状态(如属性、元素),会影响外部变量(因为指向同一对象)。
- 若直接修改参数的引用指向(如重新赋值新对象),则不会影响外部变量(因为修改的是副本引用)。
代码演示:
示例 1:修改对象内部状态
class Person {
String name;
Person(this.name);
}
void modifyPerson(Person p) {
p.name = "Bob"; // 修改对象内部状态(影响外部)
print("函数内:${p.name}"); // 输出:Bob
}
void main() {
Person person = Person("Alice");
modifyPerson(person); // 传递person引用的副本(指向同一对象)
print("函数外:${person.name}"); // 输出:Bob(内部状态被修改)
}
person变量持有一个指向Person('Alice')对象的引用(可以理解为内存地址)。- 当调用
modifyObject(person)时,传递的是这个引用的值(即内存地址)的一个副本。 - 现在,有两个引用指向同一个对象:外部的
person和函数内部的p。 - 通过函数内部的引用
p去修改对象的属性(p.name = 'Bob'),因为外部引用person也指向这同一个对象,所以这个修改对两者都是可见的。这常常被误认为是“引用传递”。
示例 2:修改参数的引用指向
class Person {
String name;
Person(this.name);
}
void replacePerson(Person p) {
p = Person("Charlie"); // 修改引用副本的指向(不影响外部)
print("函数内:${p.name}"); // 输出:Charlie
}
void main() {
Person person = Person("Alice");
replacePerson(person);
print("函数外:${person.name}"); // 输出:Alice(原始引用未变)
}
- 同样,开始时内部的
p和外部的person指向同一个对象。 - 但当执行
p = Person('Charlie')时,你是在让内部的引用p(它只是外部引用的一个副本)指向一个全新的对象。 - 这并没有改变原来那个对象(
Person('Alice'))的任何状态,也没有改变外部引用person的值。外部引用person依然坚定地指向最初的那个对象。所以外部的打印结果没有变化。
对 Flutter 开发的实际影响
-
不可变对象的优势:
对于String、int等不可变类型(以及自定义不可变类),值传递时无需担心内部状态被修改,适合作为状态管理中的数据载体(如Provider、Bloc中的状态)。 -
列表 / 映射的传递注意事项:
传递List或Map时,若需避免函数内部修改原集合,应传递副本(如List.from(list)、Map.from(map)):void safeModify(List<int> list) { list = List.from(list); // 创建副本后再修改 list.add(4); } -
状态管理中的数据更新:
在 Flutter 状态管理中,修改对象内部状态后若需触发 UI 重建,需确保状态引用发生变化(如创建新对象),因为 Widget 会对比引用是否相同来决定是否重建。
mixin extends implement之间的关系。
一句话总结
extends用于继承,建立‘is-a’的关系,继承并可重写父类实现,但受限于单继承。implements:用于实现接口,建立 “can-do” 的关系,必须重写接口所有成员,可以实现多个接口。mixin/with用于混入代码,建立 “包含” 的关系,直接复用方法实现,解决单继承局限,后写的覆盖先写的,可以混入多个。
核心概念
1. extends:类的单继承("是一个" 的关系)
- 单继承:Dart 是单继承语言,一个类只能直接继承一个父类
- 继承实现:子类继承父类的所有非私有成员(字段、方法)
- 构造函数不继承:子类不继承父类的构造函数,但可以调用它们
- 方法重写:子类可以重写父类的方法
代码演示:
基本继承示例
class Animal {
String name;
int age;
Animal(this.name, this.age);
void speak() {
print('Animal sound');
}
void sleep() {
print('$name is sleeping');
}
}
class Dog extends Animal {
String breed;
// 调用父类构造函数
Dog(String name, int age, this.breed) : super(name, age);
// 重写父类方法
@override
void speak() {
print('Woof! Woof!');
}
// 添加新方法
void fetch() {
print('$name is fetching a ball');
}
}
void main() {
var dog = Dog('Buddy', 3, 'Golden Retriever');
dog.speak(); // 输出: Woof! Woof! (重写的方法)
dog.sleep(); // 输出: Buddy is sleeping (继承的方法)
dog.fetch(); // 输出: Buddy is fetching a ball (新方法)
}
继承中的构造函数处理
class Person {
String name;
int age;
Person(this.name, this.age);
Person.newborn(String name) : this(name, 0);
void introduce() {
print('Hi, I'm $name, $age years old');
}
}
class Employee extends Person {
String company;
Employee(String name, int age, this.company) : super(name, age);
// 子类的命名构造函数
Employee.freshGraduate(String name, String company)
: this(name, 22, company);
@override
void introduce() {
super.introduce();
print('I work at $company');
}
}
属性重写(Getter/Setter)
import 'dart:math';
class Rectangle {
double width;
double height;
Rectangle(this.width, this.height);
double get area => width * height;
set area(double value) {
// 保持宽高比例
final ratio = width / height;
width = sqrt(value * ratio);
height = width / ratio;
}
}
class Square extends Rectangle {
Square(double side) : super(side, side);
@override
double get area => super.area;
@override
set area(double value) {
// 正方形面积设置
width = sqrt(value);
height = width;
}
}
继承抽象类
abstract class Animal {
String name;
Animal(this.name);
void makeSound(); // 抽象方法
void sleep() {
print('$name is sleeping');
}
}
class Cat extends Animal {
Cat(String name) : super(name);
@override
void makeSound() {
print('Meow!');
}
}
class Dog extends Animal {
Dog(String name) : super(name);
@override
void makeSound() {
print('Woof!');
}
@override
void sleep() {
super.sleep(); // 调用父类实现
print('...and dreaming about bones');
}
}
继承泛型类
class Box<T> {
T content;
Box(this.content);
T getContent() => content;
}
class NumberBox extends Box<int> {
NumberBox(int value) : super(value);
// 方法可以操作具体类型
int square() => content * content;
}
class PairBox<A, B> extends Box<A> {
B second;
PairBox(A first, this.second) : super(first);
(A, B) getPair() => (content, second);
}
继承中的 super 关键字
class Vehicle {
void start() {
print('Vehicle starting');
}
}
class Car extends Vehicle {
@override
void start() {
super.start(); // 调用父类方法
print('Car specific startup procedure');
}
}
class ElectricCar extends Car {
@override
void start() {
print('Electric systems check');
super.start(); // 调用父类(Car)的方法
}
}
继承中的静态成员
静态成员不会被继承:
class MathUtils {
static double pi = 3.14159;
static double circleArea(double radius) {
return pi * radius * radius;
}
}
class AdvancedMathUtils extends MathUtils {
// 静态成员不会被继承
// 不能直接访问 pi 或 circleArea
static double e = 2.71828;
// 必须重新定义静态方法
static double circleArea(double radius) {
return MathUtils.pi * radius * radius; // 通过类名访问
}
}
void main() {
print(MathUtils.pi); // 3.14159
// print(AdvancedMathUtils.pi); // 错误: 没有这样的getter
print(AdvancedMathUtils.e); // 2.71828
}
继承的最佳实践
- 遵循里氏替换原则:子类应该能够替换父类而不影响程序正确性
- 使用继承表示"是一个"关系:子类应该是父类的特殊化
- 优先使用组合 over 继承:如果不是真正的"是一个"关系,考虑使用组合
- 避免深继承层次:深层次的继承难以理解和维护
- 使用抽象类定义接口:为预期会被继承的类提供清晰的契约
2. implements:接口实现("像一个" 的关系)
- 接口契约:当一个类实现一个接口时,需实现接口中所有非静态成员(方法、属性、getter、setter)。
- 不继承实现:与
extends不同,implements不继承任何实现,只遵循接口定义的 “规范”。 - 多接口实现:一个类可以实现多个接口
代码演示:
实现普通类的接口:
class Vehicle {
String name;
int speed;
Vehicle(this.name, this.speed);
void move() {
print('$name is moving at $speed km/h');
}
void stop() {
print('$name has stopped');
speed = 0;
}
}
class Car implements Vehicle {
@override
String name;
@override
int speed;
Car(this.name, this.speed);
@override
void move() {
print('Car $name is driving at $speed km/h');
}
@override
void stop() {
print('Car $name is braking');
speed = 0;
}
}
实现抽象类的接口:
abstract class Animal {
String name;
int age;
Animal(this.name, this.age);
void makeSound(); // 抽象方法
void sleep() {
print('$name is sleeping');
}
}
class Dog implements Animal {
@override
String name;
@override
int age;
Dog(this.name, this.age);
@override
void makeSound() {
print('$name says: Woof!');
}
@override
void sleep() {
print('Dog $name is sleeping soundly');
}
}
静态成员在接口实现中的特殊行为
在 Dart 中,静态成员不是接口的一部分。当一个类实现另一个类或接口时,它不需要实现静态成员。这是因为静态成员属于类本身,而不是类的实例。
class ExampleWithStatic {
// 实例成员
String instanceField = 'instance';
void instanceMethod() => print('Instance method');
// 静态成员
static String staticField = 'static';
static void staticMethod() => print('Static method');
}
class Implementation implements ExampleWithStatic {
@override
String instanceField = 'implemented instance';
@override
void instanceMethod() {
print('Implemented instance method');
}
// 注意:不需要实现静态成员
// static String staticField = 'something'; // 这不是必需的
// static void staticMethod() {} // 这不是必需的
}
实现多个接口
abstract class Flyable {
void fly();
}
abstract class Swimmable {
void swim();
}
abstract class Walkable {
void walk();
}
class Duck implements Flyable, Swimmable, Walkable {
@override
void fly() {
print('Duck is flying');
}
@override
void swim() {
print('Duck is swimming');
}
@override
void walk() {
print('Duck is waddling');
}
}
实现混合接口
// 普通类
class Named {
String name;
Named(this.name);
}
// 抽象类
abstract class Aged {
int get age;
set age(int value);
}
// Mixin
mixin Colored {
String color = 'unknown';
void describeColor() {
print('Color: $color');
}
}
// 实现多个接口
class Person implements Named, Aged, Colored {
@override
String name;
@override
int age;
@override
String color;
Person(this.name, this.age, this.color);
// Colored mixin 的方法也需要实现
@override
void describeColor() {
print('Person $name has $color color');
}
}
处理私有成员
// 在同一个库中
class _PrivateClass {
int _privateValue = 0; // 私有字段
int publicValue = 0; // 公共字段
void _privateMethod() {} // 私有方法
void publicMethod() {} // 公共方法
}
// 这会报错,因为需要实现私有成员
class Implementation implements _PrivateClass {
@override
int publicValue = 0;
@override
void publicMethod() {}
// 错误: 缺少私有成员的实现
}
实现泛型接口
abstract class Repository<T> {
T findById(int id);
List<T> findAll();
void save(T entity);
void delete(int id);
}
class UserRepository implements Repository<User> {
final List<User> _users = [];
@override
User findById(int id) {
return _users.firstWhere((user) => user.id == id, orElse: () => null);
}
@override
List<User> findAll() {
return List.from(_users);
}
@override
void save(User user) {
final index = _users.indexWhere((u) => u.id == user.id);
if (index >= 0) {
_users[index] = user;
} else {
_users.add(user);
}
}
@override
void delete(int id) {
_users.removeWhere((user) => user.id == id);
}
}
class User {
final int id;
final String name;
User(this.id, this.name);
}
处理 getter 和 setter
abstract class Person {
String get firstName;
set firstName(String value);
String get lastName;
set lastName(String value);
String get fullName; // 只读属性
}
class Employee implements Person {
String _firstName;
String _lastName;
Employee(this._firstName, this._lastName);
@override
String get firstName => _firstName;
@override
set firstName(String value) {
_firstName = value;
}
@override
String get lastName => _lastName;
@override
set lastName(String value) {
_lastName = value;
}
@override
String get fullName => '$firstName $lastName';
}
实现接口时的最佳实践:
- 明确接口契约:使用抽象类或纯接口定义清晰的契约
- 避免实现包含私有成员的类:这会导致难以维护的代码
- 使用组合替代复杂接口实现:当需要复用多个类的功能时,考虑使用组合
- 保持接口简洁:遵循接口隔离原则,创建小而专注的接口
- 文档化接口:为接口提供清晰的文档说明预期行为
// 好的实践:小而专注的接口
abstract class Savable {
void save();
}
abstract class Deletable {
void delete();
}
abstract class Findable<T> {
T findById(int id);
}
class User {
final int id;
final String name;
User(this.id, this.name);
}
// 实现多个小接口
class UserRepository implements Savable, Deletable, Findable<User> {
@override
void delete() {
// TODO: implement delete
}
@override
User findById(int id) {
// TODO: implement findById
throw UnimplementedError();
}
@override
void save() {
// TODO: implement save
}
// 实现...
}
mixin:代码复用("包含" 的关系)
- 用
mixin关键字定义,不能有构造函数(避免与混入类的构造函数冲突)。 - 一个类可通过
with关键字混入多个 mixin(按顺序生效,后混入的 mixin 会覆盖先混入的同名方法)。 - 可通过
on关键字限制 mixin 的使用范围(如mixin M on A表示 M 只能混入 A 的子类)。
代码演示:
声明限制
Mixin 不能有构造函数,也不能被实例化:
mixin Logger {
// 不能有构造函数
// Logger(); // 错误: Mixin 不能声明构造函数
void log(String message) {
print('LOG: $message');
}
}
使用 on 关键字限制应用范围
class Animal {
void breathe() {
print('Breathing');
}
}
// 这个 Mixin 只能用于 Animal 或其子类
mixin Swimming on Animal {
void swim() {
print('Swimming');
breathe(); // 可以访问 Animal 的方法
}
}
// 正确: Bird 是 Animal 的子类
class Bird extends Animal with Swimming {}
// 错误: Robot 不是 Animal 的子类
// class Robot with Swimming {} // 编译错误
Mixin 的线性化
Dart 使用线性化算法来确定方法解析顺序。当类使用多个 Mixin 时,方法的解析顺序是从右到左:
mixin A {
void method() {
print('A.method');
}
}
mixin B {
void method() {
print('B.method');
}
}
mixin C {
void method() {
print('C.method');
}
}
class MyClass with A, B, C {
// 方法解析顺序: MyClass -> C -> B -> A -> Object
}
void main() {
var obj = MyClass();
obj.method(); // 输出: C.method (最右边的 Mixin 优先)
}
Mixin 的最佳实践
- 单一职责:每个 Mixin 应该只负责一个特定的功能
- 命名清晰:使用描述性的名称,通常以 "-able" 或 "-er" 结尾
- 避免状态污染:谨慎使用实例变量,避免意外的状态共享
- 文档化约束:使用
on关键字明确指定 Mixin 的使用范围 - 测试独立:确保 Mixin 可以独立测试,不依赖于特定的类层次结构
Dart中如何取消正在执行中的异步任务?
一句话总结
- 通过
Completer来控制Future的完成状态。 - 通过
CancelableOperation包装Future,实现 “逻辑取消”。 - 基于流的操作,可以使用
StreamSubscription的取消机制。 - 对于计算密集型任务,使用 Isolate 通过
kill()方法强制终止。
核心概念
1. 使用 Future 和 Completer 实现取消
Completer是 Dart 中用于手动控制Future完成状态的工具类,它可以主动触发Future的完成(成功 / 失败)。
import 'dart:async';
class CancellableTask {
Completer<void> _completer = Completer<void>();
Future<void> execute() async {
_completer = Completer<void>();
_doWork();
}
Future<void> _doWork() async {
for (int i = 0; i < 100; i++) {
// 检查是否被取消
if (_completer.isCompleted) {
print('任务被取消');
return;
}
await Future.delayed(Duration(milliseconds: 100));
print('处理项目 $i');
}
return _completer.future;
}
void cancel() {
if (!_completer.isCompleted) {
_completer.complete();
}
}
}
// 使用示例
void main() async {
final task = CancellableTask();
// 启动任务
task.execute();
// 2秒后取消
await Future.delayed(Duration(seconds: 2));
task.cancel();
}
2. 使用 CancelableOperation (package:async)
- 需要安装
async库。CancelableOperation本质是对Future的封装,通过一个 “取消令牌” 标记任务状态。取消后,后续的then/whenComplete等回调不会执行,且会触发onCancel回调释放资源。 - 第三方库(如
dio)通常封装了取消机制,本质是基于CancelableOperation或标志位。
import 'dart:async';
import 'package:async/async.dart';
// 模拟耗时任务(如网络请求)
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 3)); // 模拟3秒耗时
return "请求结果";
}
void main() async {
// 包装Future为可取消操作
final cancelable = CancelableOperation.fromFuture(
fetchData(),
onCancel: () {
// 取消时释放资源(如关闭网络连接)
print("任务已取消,释放资源");
},
);
// 1秒后取消任务
Timer(Duration(seconds: 1), () {
cancelable.cancel(); // 取消操作
});
// 监听结果(取消后不会执行)
final result = await cancelable.value;
print("任务完成:$result");
}
- 取消后,原始
Future可能仍在后台执行(无法强制终止),但结果会被忽略。 - 适合处理 “即使取消也不会造成严重资源浪费” 的任务(如短时间网络请求)。
3.Stream任务的取消:通过StreamSubscription控制
通过stream.listen()获取StreamSubscription对象,调用其cancel()方法可终止流的事件传递,且会触发流的onCancel回调释放资源。
import 'dart:async';
void main() {
// 创建定时发送事件的Stream(每1秒一次)
final stream = Stream.periodic(Duration(seconds: 1), (count) => count);
// 订阅流,获取订阅对象
final subscription = stream.listen(
(data) => print("接收事件:$data"),
onDone: () => print("流已完成"),
);
// 3秒后取消订阅
Timer(Duration(seconds: 3), () {
subscription.cancel(); // 取消后,不再接收事件
print("流已取消");
});
}
// 输出:
// 接收事件:0
// 接收事件:1
// 接收事件:2
// 流已取消
4.Isolate任务的取消:通过kill()强制终止
Isolate拥有独立的内存和执行线程,kill()会直接终止其运行,释放所有资源,终止后无法恢复。
import 'dart:async';
import 'dart:isolate';
// 耗时计算任务(在Isolate中执行)
void heavyCompute(SendPort sendPort) {
int result = 0;
for (int i = 0; i < 1000000000; i++) {
result += i;
// 定期检查是否需要退出(可选,优化终止响应速度)
if (i % 100000000 == 0) {
print("计算中:$i");
}
}
sendPort.send(result);
}
void main() async {
// 创建端口接收结果
final receivePort = ReceivePort();
// 启动Isolate
final isolate = await Isolate.spawn(heavyCompute, receivePort.sendPort);
// 监听结果
receivePort.listen((data) {
print("计算结果:$data");
receivePort.close();
});
// 2秒后终止Isolate(取消任务)
Timer(Duration(seconds: 2), () {
isolate.kill(priority: Isolate.immediate); // 立即终止
print("Isolate已终止");
receivePort.close();
});
}
kill()是 “暴力终止”,需确保 Isolate 中没有未释放的资源(如文件句柄)。- 建议在 Isolate 内部添加 “取消检查点”(如定期检查标志),配合
kill()提升终止效率。
Dart中Future和Stream区别?
一句话总结
Future:处理单次异步结果,表示一个未来可能完成的单个异步操作,结果只有两种状态 ——“成功返回一个值” 或 “失败抛出一个错误”。Stream:处理连续异步事件流,表示一个异步事件序列,可以持续产生多个值(或错误),最终可能正常结束或出错终止。
核心概念
Future表示单个异步操作的结果,它有三种状态:
未完成: 异步操作正在进行中;
已完成: 成功(返回一个结果) 失败(返回一个错误)
常用于 “一次性” 异步操作,如网络请求、文件读取、延迟执行等。
就像是一次性的外卖订单:你下单,等待,然后收到一份完整的餐点。
Stream表示一系列异步事件的序列,可以持续产生多个结果(或错误),类似 “数据流”。它的核心是 “订阅 - 发射” 模式。就像是餐厅的传送带:食物(数据)会连续不断地传送过来,你可以随时拿取。
-
通过
stream.listen()订阅事件; -
异步操作可以通过
add()持续发射数据、addError()发射错误,最后通过close()结束流。 -
可以产生多个结果(零个、一个或多个),是 “持续的异步操作”;
-
常用于 “多次触发” 的场景,如实时数据更新(WebSocket 消息、传感器数据、文件分片读取)。 用一个简单的比喻:
代码演示:
Future 示例:获取单个异步结果
class User {
final int id;
final String name;
User({required this.id, required this.name});
}
// Future: 获取用户信息(单次操作)
Future<User> fetchUserData(int userId) async {
// 模拟网络请求
await Future.delayed(Duration(seconds: 2));
return User(id: userId, name: 'John Doe');
}
// 使用
void main() async {
print('开始获取用户数据...');
final user = await fetchUserData(1);
print('用户数据: ${user.name}'); // 单次结果
}
Stream 示例:监听连续的数据流
class Location {
final double latitude;
final double longitude;
Location({required this.latitude, required this.longitude});
}
// Stream: 监听实时位置更新
Stream<Location> getLocationUpdates() async* {
// 模拟连续的位置更新
for (int i = 0; i < 5; i++) {
await Future.delayed(Duration(seconds: 1));
yield Location(latitude: 37.0 + i * 0.1, longitude: -122.0 + i * 0.1);
}
}
// 使用
void main() {
print('开始监听位置更新...');
final subscription = getLocationUpdates().listen(
(location) {
print('位置更新: ${location.latitude}, ${location.longitude}');
},
onDone: () => print('监听完成'),
onError: (error) => print('错误: $error'),
);
// 可以随时取消监听
// subscription.cancel();
}
Future 的典型使用场景:
// 网络请求:HTTP API 调用
Future<Response> response = http.get(Uri.parse('https://api.example.com/data'));
// 文件读写:读取/写入文件
Future<String> contents = File('data.txt').readAsString();
// 数据库操作:单次查询
Future<User> user = database.findUser(1);
// 用户交互:显示对话框
Future<bool> choice = showDialog(
context: context,
builder: (context) => AlertDialog(...),
);
Stream 的典型使用场景:
// 实时数据:WebSocket、Firestore 实时更新
Stream<QuerySnapshot> stream = Firestore.instance.collection('messages').snapshots();
// 用户输入:搜索框输入监听
searchController.textChanges
.debounceTime(Duration(milliseconds: 300))
.listen((query) => search(query));
// 传感器数据:位置、加速度计更新
Stream<Location> locationStream = Geolocator.getPositionStream();
// 状态管理:BLoC、Riverpod 的状态流
Stream<AppState> appStateStream = bloc.stream;
性能和使用建议:
何时选择 Future
- 只需要单次异步结果时
- 操作有明确的开始和结束时
- 不需要中间状态更新时
- 简单的异步操作
何时选择 Stream
- 需要处理连续的数据序列时
- 需要实时更新和状态变化时
- 需要取消和暂停机制时
- 复杂的事件处理场景
- 避免 Stream 过度使用:如果 Future 足够,不要使用 Stream。
- 及时取消订阅:防止内存泄漏。
- 使用 StreamBuilder 优化 UI。