theme: orange
文章目錄
- Dart是不是單線程模型?是如何運行的?
- dart是值傳遞還是引用傳遞?
- mixin extends implement之間的關系。
- Dart中如何取消正在執(zhí)行中的異步任務?
- Dart中Future和Stream區(qū)別?
Dart是不是單線程模型?是如何運行的?
一句話總結
- Dart 是單線程模型,它的核心是事件循環(huán)機制,通過微任務隊列和事件隊列來高效地處理異步操作,從而保證了UI線程的流暢性。
核心概念
Dart是單線程模型,不必像在Java或C#中那樣擔心共享內(nèi)存的鎖競爭和線程同步問題。運行機制可以從以下三個層面來理解:
1. 核心機制:事件循環(huán) (Event Loop)
Dart 的事件循環(huán)依賴兩個有序隊列,按優(yōu)先級執(zhí)行:
-
微任務隊列 (Microtask Queue) :優(yōu)先級最高;通過
scheduleMicrotask()顯式添加,附著在這個 Future 上的回調(diào)(包括then、catchError、whenComplete以及await的續(xù)延代碼)都會被調(diào)度到微任務隊列中執(zhí)行。 -
事件隊列 (Event Queue) :我們?nèi)粘J褂玫慕^大部分異步操作都來源于這里,例如
I/O操作(文件、網(wǎng)絡請求)、手勢識別、繪圖消息、Timer(Future.delayed)。
事件循環(huán)的工作流程圖:
flowchart TD
A[事件循環(huán)啟動] --> B{微任務隊列<br>是否為空?}
B -- 否 --> C[取出并執(zhí)行<br>一個微任務]
C --> B
B -- 是 --> D{事件隊列<br>是否為空?}
D -- 否 --> E[取出并執(zhí)行<br>一個事件]
E --> F[事件執(zhí)行完畢]
F --> B
D -- 是 --> G[等待新事件到來]
G --> B
事件循環(huán)代碼演示:
void main() {
// ------------------------------
// 1. 同步代碼(優(yōu)先執(zhí)行,事件循環(huán)啟動前)
// ------------------------------
print("1. 同步代碼 - 開始執(zhí)行");
// ------------------------------
// 2. 微任務隊列(Microtask Queue):優(yōu)先級高于事件隊列
// 通過 Future.microtask 添加
// ------------------------------
Future.microtask(() {
print("2. 微任務A - 微任務隊列");
});
// ------------------------------
// 3. 事件隊列(Event Queue):優(yōu)先級低于微任務隊列
// 通過 Future 或 Future.delayed 添加(即使延遲0秒)
// ------------------------------
// 事件1:執(zhí)行時會新增微任務
Future(() {
print("4. 事件1 - 事件隊列(開始執(zhí)行)");
// 在事件執(zhí)行過程中新增微任務
Future.microtask(() {
print("5. 微任務B(事件1中新增) - 微任務隊列");
});
print("6. 事件1 - 事件隊列(執(zhí)行結束)");
});
// 再添加一個微任務(微任務隊列按添加順序執(zhí)行)
Future.microtask(() {
print("3. 微任務C - 微任務隊列");
});
// 事件2:在事件1之后執(zhí)行
Future.delayed(Duration.zero, () {
print("7. 事件2 - 事件隊列");
});
// ------------------------------
// 1. 同步代碼(繼續(xù)執(zhí)行)
// ------------------------------
print("8. 同步代碼 - 執(zhí)行結束(事件循環(huán)即將啟動)");
}
執(zhí)行結果:
1. 同步代碼 - 開始執(zhí)行
8. 同步代碼 - 執(zhí)行結束(事件循環(huán)即將啟動)
2. 微任務A - 微任務隊列
3. 微任務C - 微任務隊列
4. 事件1 - 事件隊列(開始執(zhí)行)
6. 事件1 - 事件隊列(執(zhí)行結束)
5. 微任務B(事件1中新增) - 微任務隊列
7. 事件2 - 事件隊列
步驟拆解(對應執(zhí)行結果):
-
同步代碼優(yōu)先執(zhí)行:
打印1和8(同步代碼不進入任何隊列,事件循環(huán)啟動前就執(zhí)行完畢)。 -
事件循環(huán)啟動,先清空微任務隊列:
按添加順序執(zhí)行微任務 A 和 C,打印2和3(微任務隊列此時為空)。 -
處理事件隊列的第一個事件(事件 1) :
- 執(zhí)行事件 1 的代碼,打印
4和6。 - 事件 1 執(zhí)行過程中新增微任務 B(加入微任務隊列)。
- 執(zhí)行事件 1 的代碼,打印
-
事件 1 執(zhí)行完畢后,再次清空微任務隊列:
執(zhí)行新增的微任務 B,打印5(微任務隊列再次為空)。 -
處理事件隊列的下一個事件(事件 2) :
執(zhí)行事件 2,打印7(事件隊列此時為空)。 - 所有隊列清空,程序結束。
2. 實現(xiàn)異步的關鍵:Future 和 async/await
-
Future:它不是一個并行計算的結果,而是一個 “承諾” ,承諾在未來某個事件循環(huán)輪次中給你一個值(或一個錯誤)。當你調(diào)用
http.get()或Future.delayed()時,Dart會立刻返回一個Future對象,并立即返回(不會阻塞),I/O操作(網(wǎng)絡/文件)由操作系統(tǒng)異步處理(Dart線程之外),主線程(Dart線程)僅處理回調(diào)。 -
async/await:這只是編寫異步代碼的 “語法糖” ,讓異步代碼看起來和同步代碼一樣直觀。在函數(shù)前加上
async關鍵字,其返回值會被自動包裝為Future。await關鍵字的作用是:告訴事件循環(huán)“我這邊有個Future需要一些時間才能完成,你先去處理其它事件(微任務或UI事件),等這個Future有結果了再回來繼續(xù)執(zhí)行我后面的代碼?!?/strong> 這非常重要,它不會阻塞線程,而是讓出執(zhí)行權。
代碼演示:
// 需安裝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: 收到數(shù)據(jù): $data');
}
void main() {
loadData();
print('2: 執(zhí)行其它操作');
}
執(zhí)行結果:
1: 開始請求
2: 執(zhí)行其它操作
3: 收到數(shù)據(jù): Instance of 'Response'
梳理流程:
-
發(fā)起請求(同步) :
-
http.get()被調(diào)用。 - 它同步地創(chuàng)建一個
Future對象并返回。 - 它同步地發(fā)起一個系統(tǒng)調(diào)用(比如用
libcurl或系統(tǒng)的 HTTP 庫),將這個網(wǎng)絡 I/O 操作委托給操作系統(tǒng)在 Dart 線程之外執(zhí)行。此時,Dart 線程本身是空閑的,可以繼續(xù)執(zhí)行事件循環(huán)。
-
-
遇到
await(同步 -> 異步) :-
await關鍵字檢查它右邊的Future。如果這個 Future 尚未完成(http.get返回的肯定未完成),它會暫停當前函數(shù)的執(zhí)行。 - 它向這個未完成的 Future 訂閱一個回調(diào)。這個回調(diào)包含了
await之后的所有代碼(即print('3: ...'))。 - 這個回調(diào)的調(diào)度機制,與
future.then(...)的回調(diào)調(diào)度機制是完全相同的。
-
-
等待與完成(在Dart線程外) :
- Dart 事件循環(huán)繼續(xù)運行,處理微任務和UI事件(比如執(zhí)行
print('2'))。 - 操作系統(tǒng)在后臺處理網(wǎng)絡請求。
- Dart 事件循環(huán)繼續(xù)運行,處理微任務和UI事件(比如執(zhí)行
-
請求完成,通知Dart(事件隊列) :
- 當操作系統(tǒng)完成網(wǎng)絡請求后,它會通過一種機制(如
epoll、kqueue或IOCP)通知 Dart 運行時。 -
這個“通知”作為一個事件,被放入 Dart 的
事件隊列(Event Queue)中。 這個事件本身不包含數(shù)據(jù)處理邏輯,只是一個信號。
- 當操作系統(tǒng)完成網(wǎng)絡請求后,它會通過一種機制(如
-
事件循環(huán)處理完成事件(從事件隊列到微任務隊列) :
- 事件循環(huán)從事件隊列中取出這個“網(wǎng)絡請求完成”的事件。
- 處理這個事件的代碼(Dart 運行時內(nèi)部代碼)會完成之前
http.get返回的那個 Future 對象(即調(diào)用_completer.complete(response))。 - 最關鍵的一步來了:當
future.complete()被調(diào)用時,Dart 的運行時會 schedule 一個微任務。這個微任務的任務就是:執(zhí)行所有通過then、catchError或await注冊在這個 Future 上的回調(diào)函數(shù)。
-
執(zhí)行回調(diào)(微任務隊列) :
- 當事件循環(huán)處理完當前的事件后,它會檢查微任務隊列。
- 它發(fā)現(xiàn)了第5步中被調(diào)度的微任務,于是執(zhí)行它。
- 這個微任務的內(nèi)容就是執(zhí)行
print('3: 收到數(shù)據(jù): $data')。
操作系統(tǒng)完成I/O -> 發(fā)送信號至 Dart 事件隊列 -> 事件循環(huán)處理該信號 -> Future.complete()被調(diào)用 -> 調(diào)度一個微任務來執(zhí)行所有回調(diào) -> 事件循環(huán)處理微任務 -> 執(zhí)行 await 之后的代碼。
3. 處理真正耗時計算:Isolate
事件循環(huán)和異步處理對于I/O操作是完美的,但對于CPU密集型計算(如圖像處理、加密解密、復雜JSON解析),如果計算本身就在Dart線程上運行,它就會阻塞事件循環(huán),導致隊列中的所有其他任務(包括UI渲染)都無法處理,應用就會卡頓。
Dart的解決方案是 Isolate。
- 不共享內(nèi)存:每個Isolate都有自己的獨立內(nèi)存堆(Heap)和事件循環(huán)。Isolate之間不共享任何狀態(tài),通信的唯一方式是通過消息傳遞(Passing Messages) 。
- 真正的并行:由于現(xiàn)代設備是多核CPU,不同的Isolate可以被調(diào)度到不同的CPU核心上真正地并行運行。
-
通信成本:因為不共享內(nèi)存,Isolate之間傳遞消息時,數(shù)據(jù)會發(fā)生拷貝。雖然對于簡單數(shù)據(jù)很快,但對于大型數(shù)據(jù)(如圖片、大列表),這個拷貝成本會很高。通常使用
SendPort和ReceivePort進行通信。
在Flutter中,你可以使用Isolate.spawn()或更高級的compute()函數(shù)來將繁重任務拋到新的Isolate中執(zhí)行。
代碼演示:
import 'package:flutter/foundation.dart';
// 使用 compute (Flutter提供的簡便API)
void main() async {
var result = await compute(heavyTask, 1000000000);
print(result);
}
// 這個函數(shù)會在新的Isolate中執(zhí)行
int heavyTask(int count) {
int sum = 0;
for (int i = 0; i < count; i++) {
sum += i;
}
return sum;
}
dart是值傳遞還是引用傳遞?
一句話總結
- Dart 是嚴格的值傳遞語言,所有參數(shù)傳遞的都是 “值的副本”。
核心概念
Dart 中的數(shù)據(jù)類型分為基本類型(Primitive Types) 和對象類型(Object Types) ,兩者在值傳遞時的表現(xiàn)不同,容易造成 “引用傳遞” 的誤解:
1. 基本類型(數(shù)值、布爾、字符串、null)
基本類型的值傳遞是直接傳遞值的副本,函數(shù)內(nèi)部修改參數(shù)不會影響外部變量。
代碼演示:
void modifyInt(int a) {
a = 100; // 修改的是副本
print("函數(shù)內(nèi):$a"); // 輸出:100
}
void main() {
int x = 10;
modifyInt(x); // 傳遞x的副本(值為10)
print("函數(shù)外:$x"); // 輸出:10(原始值未變)
}
- 解析:
x的值10被復制一份傳遞給a,函數(shù)內(nèi)修改a不會影響x,符合值傳遞特性。
2. 對象類型(類實例、列表、映射等)
對象類型的值傳遞是傳遞對象引用的副本(即內(nèi)存地址的副本)。這意味著:
- 函數(shù)內(nèi)部和外部的變量持有同一個對象的不同引用副本(但指向同一塊內(nèi)存)。
- 若修改對象的內(nèi)部狀態(tài)(如屬性、元素),會影響外部變量(因為指向同一對象)。
- 若直接修改參數(shù)的引用指向(如重新賦值新對象),則不會影響外部變量(因為修改的是副本引用)。
代碼演示:
示例 1:修改對象內(nèi)部狀態(tài)
class Person {
String name;
Person(this.name);
}
void modifyPerson(Person p) {
p.name = "Bob"; // 修改對象內(nèi)部狀態(tài)(影響外部)
print("函數(shù)內(nèi):${p.name}"); // 輸出:Bob
}
void main() {
Person person = Person("Alice");
modifyPerson(person); // 傳遞person引用的副本(指向同一對象)
print("函數(shù)外:${person.name}"); // 輸出:Bob(內(nèi)部狀態(tài)被修改)
}
-
person變量持有一個指向Person('Alice')對象的引用(可以理解為內(nèi)存地址)。 - 當調(diào)用
modifyObject(person)時,傳遞的是這個引用的值(即內(nèi)存地址)的一個副本。 - 現(xiàn)在,有兩個引用指向同一個對象:外部的
person和函數(shù)內(nèi)部的p。 - 通過函數(shù)內(nèi)部的引用
p去修改對象的屬性(p.name = 'Bob'),因為外部引用person也指向這同一個對象,所以這個修改對兩者都是可見的。這常常被誤認為是“引用傳遞”。
示例 2:修改參數(shù)的引用指向
class Person {
String name;
Person(this.name);
}
void replacePerson(Person p) {
p = Person("Charlie"); // 修改引用副本的指向(不影響外部)
print("函數(shù)內(nèi):${p.name}"); // 輸出:Charlie
}
void main() {
Person person = Person("Alice");
replacePerson(person);
print("函數(shù)外:${person.name}"); // 輸出:Alice(原始引用未變)
}
- 同樣,開始時內(nèi)部的
p和外部的person指向同一個對象。 - 但當執(zhí)行
p = Person('Charlie')時,你是在讓內(nèi)部的引用p(它只是外部引用的一個副本)指向一個全新的對象。 - 這并沒有改變原來那個對象(
Person('Alice'))的任何狀態(tài),也沒有改變外部引用person的值。外部引用person依然堅定地指向最初的那個對象。所以外部的打印結果沒有變化。
對 Flutter 開發(fā)的實際影響
不可變對象的優(yōu)勢:
對于String、int等不可變類型(以及自定義不可變類),值傳遞時無需擔心內(nèi)部狀態(tài)被修改,適合作為狀態(tài)管理中的數(shù)據(jù)載體(如Provider、Bloc中的狀態(tài))。-
列表 / 映射的傳遞注意事項:
傳遞List或Map時,若需避免函數(shù)內(nèi)部修改原集合,應傳遞副本(如List.from(list)、Map.from(map)):void safeModify(List<int> list) { list = List.from(list); // 創(chuàng)建副本后再修改 list.add(4); } 狀態(tài)管理中的數(shù)據(jù)更新:
在 Flutter 狀態(tài)管理中,修改對象內(nèi)部狀態(tài)后若需觸發(fā) UI 重建,需確保狀態(tài)引用發(fā)生變化(如創(chuàng)建新對象),因為 Widget 會對比引用是否相同來決定是否重建。
mixin extends implement之間的關系。
一句話總結
-
extends用于繼承,建立‘is-a’的關系,繼承并可重寫父類實現(xiàn),但受限于單繼承。 -
implements:用于實現(xiàn)接口,建立 “can-do” 的關系,必須重寫接口所有成員,可以實現(xiàn)多個接口。 -
mixin/with用于混入代碼,建立 “包含” 的關系,直接復用方法實現(xiàn),解決單繼承局限,后寫的覆蓋先寫的,可以混入多個。
核心概念
1. extends:類的單繼承("是一個" 的關系)
- 單繼承:Dart 是單繼承語言,一個類只能直接繼承一個父類
- 繼承實現(xiàn):子類繼承父類的所有非私有成員(字段、方法)
- 構造函數(shù)不繼承:子類不繼承父類的構造函數(shù),但可以調(diào)用它們
- 方法重寫:子類可以重寫父類的方法
代碼演示:
基本繼承示例
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;
// 調(diào)用父類構造函數(shù)
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 (新方法)
}
繼承中的構造函數(shù)處理
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);
// 子類的命名構造函數(shù)
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(); // 調(diào)用父類實現(xiàn)
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(); // 調(diào)用父類方法
print('Car specific startup procedure');
}
}
class ElectricCar extends Car {
@override
void start() {
print('Electric systems check');
super.start(); // 調(diào)用父類(Car)的方法
}
}
繼承中的靜態(tài)成員
靜態(tài)成員不會被繼承:
class MathUtils {
static double pi = 3.14159;
static double circleArea(double radius) {
return pi * radius * radius;
}
}
class AdvancedMathUtils extends MathUtils {
// 靜態(tài)成員不會被繼承
// 不能直接訪問 pi 或 circleArea
static double e = 2.71828;
// 必須重新定義靜態(tài)方法
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
}
繼承的最佳實踐
- 遵循里氏替換原則:子類應該能夠替換父類而不影響程序正確性
- 使用繼承表示"是一個"關系:子類應該是父類的特殊化
- 優(yōu)先使用組合 over 繼承:如果不是真正的"是一個"關系,考慮使用組合
- 避免深繼承層次:深層次的繼承難以理解和維護
- 使用抽象類定義接口:為預期會被繼承的類提供清晰的契約
2. implements:接口實現(xiàn)("像一個" 的關系)
- 接口契約:當一個類實現(xiàn)一個接口時,需實現(xiàn)接口中所有非靜態(tài)成員(方法、屬性、getter、setter)。
-
不繼承實現(xiàn):與
extends不同,implements不繼承任何實現(xiàn),只遵循接口定義的 “規(guī)范”。 - 多接口實現(xiàn):一個類可以實現(xiàn)多個接口
代碼演示:
實現(xiàn)普通類的接口:
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;
}
}
實現(xiàn)抽象類的接口:
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');
}
}
靜態(tài)成員在接口實現(xiàn)中的特殊行為
在 Dart 中,靜態(tài)成員不是接口的一部分。當一個類實現(xiàn)另一個類或接口時,它不需要實現(xiàn)靜態(tài)成員。這是因為靜態(tài)成員屬于類本身,而不是類的實例。
class ExampleWithStatic {
// 實例成員
String instanceField = 'instance';
void instanceMethod() => print('Instance method');
// 靜態(tài)成員
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');
}
// 注意:不需要實現(xiàn)靜態(tài)成員
// static String staticField = 'something'; // 這不是必需的
// static void staticMethod() {} // 這不是必需的
}
實現(xiàn)多個接口
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');
}
}
實現(xiàn)混合接口
// 普通類
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');
}
}
// 實現(xiàn)多個接口
class Person implements Named, Aged, Colored {
@override
String name;
@override
int age;
@override
String color;
Person(this.name, this.age, this.color);
// Colored mixin 的方法也需要實現(xiàn)
@override
void describeColor() {
print('Person $name has $color color');
}
}
處理私有成員
// 在同一個庫中
class _PrivateClass {
int _privateValue = 0; // 私有字段
int publicValue = 0; // 公共字段
void _privateMethod() {} // 私有方法
void publicMethod() {} // 公共方法
}
// 這會報錯,因為需要實現(xiàn)私有成員
class Implementation implements _PrivateClass {
@override
int publicValue = 0;
@override
void publicMethod() {}
// 錯誤: 缺少私有成員的實現(xiàn)
}
實現(xiàn)泛型接口
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';
}
實現(xiàn)接口時的最佳實踐:
- 明確接口契約:使用抽象類或純接口定義清晰的契約
- 避免實現(xiàn)包含私有成員的類:這會導致難以維護的代碼
- 使用組合替代復雜接口實現(xiàn):當需要復用多個類的功能時,考慮使用組合
- 保持接口簡潔:遵循接口隔離原則,創(chuàng)建小而專注的接口
- 文檔化接口:為接口提供清晰的文檔說明預期行為
// 好的實踐:小而專注的接口
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);
}
// 實現(xiàn)多個小接口
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
}
// 實現(xiàn)...
}
mixin:代碼復用("包含" 的關系)
- 用
mixin關鍵字定義,不能有構造函數(shù)(避免與混入類的構造函數(shù)沖突)。 - 一個類可通過
with關鍵字混入多個 mixin(按順序生效,后混入的 mixin 會覆蓋先混入的同名方法)。 - 可通過
on關鍵字限制 mixin 的使用范圍(如mixin M on A表示 M 只能混入 A 的子類)。
代碼演示:
聲明限制
Mixin 不能有構造函數(shù),也不能被實例化:
mixin Logger {
// 不能有構造函數(shù)
// Logger(); // 錯誤: Mixin 不能聲明構造函數(shù)
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 優(yōu)先)
}
Mixin 的最佳實踐
- 單一職責:每個 Mixin 應該只負責一個特定的功能
- 命名清晰:使用描述性的名稱,通常以 "-able" 或 "-er" 結尾
- 避免狀態(tài)污染:謹慎使用實例變量,避免意外的狀態(tài)共享
-
文檔化約束:使用
on關鍵字明確指定 Mixin 的使用范圍 - 測試獨立:確保 Mixin 可以獨立測試,不依賴于特定的類層次結構
Dart中如何取消正在執(zhí)行中的異步任務?
一句話總結
- 通過
Completer來控制Future的完成狀態(tài)。 - 通過
CancelableOperation包裝Future,實現(xiàn) “邏輯取消”。 - 基于流的操作,可以使用
StreamSubscription的取消機制。 - 對于計算密集型任務,使用 Isolate 通過
kill()方法強制終止。
核心概念
1. 使用 Future 和 Completer 實現(xiàn)取消
Completer是 Dart 中用于手動控制Future完成狀態(tài)的工具類,它可以主動觸發(fā)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本質(zhì)是對Future的封裝,通過一個 “取消令牌” 標記任務狀態(tài)。取消后,后續(xù)的then/whenComplete等回調(diào)不會執(zhí)行,且會觸發(fā)onCancel回調(diào)釋放資源。 - 第三方庫(如
dio)通常封裝了取消機制,本質(zhì)是基于CancelableOperation或標志位。
import 'dart:async';
import 'package:async/async.dart';
// 模擬耗時任務(如網(wǎng)絡請求)
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 3)); // 模擬3秒耗時
return "請求結果";
}
void main() async {
// 包裝Future為可取消操作
final cancelable = CancelableOperation.fromFuture(
fetchData(),
onCancel: () {
// 取消時釋放資源(如關閉網(wǎng)絡連接)
print("任務已取消,釋放資源");
},
);
// 1秒后取消任務
Timer(Duration(seconds: 1), () {
cancelable.cancel(); // 取消操作
});
// 監(jiān)聽結果(取消后不會執(zhí)行)
final result = await cancelable.value;
print("任務完成:$result");
}
- 取消后,原始
Future可能仍在后臺執(zhí)行(無法強制終止),但結果會被忽略。 - 適合處理 “即使取消也不會造成嚴重資源浪費” 的任務(如短時間網(wǎng)絡請求)。
3.Stream任務的取消:通過StreamSubscription控制
通過stream.listen()獲取StreamSubscription對象,調(diào)用其cancel()方法可終止流的事件傳遞,且會觸發(fā)流的onCancel回調(diào)釋放資源。
import 'dart:async';
void main() {
// 創(chuàng)建定時發(fā)送事件的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擁有獨立的內(nèi)存和執(zhí)行線程,kill()會直接終止其運行,釋放所有資源,終止后無法恢復。
import 'dart:async';
import 'dart:isolate';
// 耗時計算任務(在Isolate中執(zhí)行)
void heavyCompute(SendPort sendPort) {
int result = 0;
for (int i = 0; i < 1000000000; i++) {
result += i;
// 定期檢查是否需要退出(可選,優(yōu)化終止響應速度)
if (i % 100000000 == 0) {
print("計算中:$i");
}
}
sendPort.send(result);
}
void main() async {
// 創(chuàng)建端口接收結果
final receivePort = ReceivePort();
// 啟動Isolate
final isolate = await Isolate.spawn(heavyCompute, receivePort.sendPort);
// 監(jiān)聽結果
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 內(nèi)部添加 “取消檢查點”(如定期檢查標志),配合
kill()提升終止效率。
Dart中Future和Stream區(qū)別?
一句話總結
-
Future:處理單次異步結果,表示一個未來可能完成的單個異步操作,結果只有兩種狀態(tài) ——“成功返回一個值” 或 “失敗拋出一個錯誤”。 -
Stream:處理連續(xù)異步事件流,表示一個異步事件序列,可以持續(xù)產(chǎn)生多個值(或錯誤),最終可能正常結束或出錯終止。
核心概念
用一個簡單的比喻:
- Future 就像是一次性的外賣訂單:你下單,等待,然后收到一份完整的餐點。
- Stream 就像是餐廳的傳送帶:食物(數(shù)據(jù))會連續(xù)不斷地傳送過來,你可以隨時拿取。
代碼演示:
Future 示例:獲取單個異步結果
class User {
final int id;
final String name;
User({required this.id, required this.name});
}
// Future: 獲取用戶信息(單次操作)
Future<User> fetchUserData(int userId) async {
// 模擬網(wǎng)絡請求
await Future.delayed(Duration(seconds: 2));
return User(id: userId, name: 'John Doe');
}
// 使用
void main() async {
print('開始獲取用戶數(shù)據(jù)...');
final user = await fetchUserData(1);
print('用戶數(shù)據(jù): ${user.name}'); // 單次結果
}
Stream 示例:監(jiān)聽連續(xù)的數(shù)據(jù)流
class Location {
final double latitude;
final double longitude;
Location({required this.latitude, required this.longitude});
}
// Stream: 監(jiān)聽實時位置更新
Stream<Location> getLocationUpdates() async* {
// 模擬連續(xù)的位置更新
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('開始監(jiān)聽位置更新...');
final subscription = getLocationUpdates().listen(
(location) {
print('位置更新: ${location.latitude}, ${location.longitude}');
},
onDone: () => print('監(jiān)聽完成'),
onError: (error) => print('錯誤: $error'),
);
// 可以隨時取消監(jiān)聽
// subscription.cancel();
}
Future 的典型使用場景:
// 網(wǎng)絡請求:HTTP API 調(diào)用
Future<Response> response = http.get(Uri.parse('https://api.example.com/data'));
// 文件讀寫:讀取/寫入文件
Future<String> contents = File('data.txt').readAsString();
// 數(shù)據(jù)庫操作:單次查詢
Future<User> user = database.findUser(1);
// 用戶交互:顯示對話框
Future<bool> choice = showDialog(
context: context,
builder: (context) => AlertDialog(...),
);
Stream 的典型使用場景:
// 實時數(shù)據(jù):WebSocket、Firestore 實時更新
Stream<QuerySnapshot> stream = Firestore.instance.collection('messages').snapshots();
// 用戶輸入:搜索框輸入監(jiān)聽
searchController.textChanges
.debounceTime(Duration(milliseconds: 300))
.listen((query) => search(query));
// 傳感器數(shù)據(jù):位置、加速度計更新
Stream<Location> locationStream = Geolocator.getPositionStream();
// 狀態(tài)管理:BLoC、Riverpod 的狀態(tài)流
Stream<AppState> appStateStream = bloc.stream;
性能和使用建議:
何時選擇 Future
- 只需要單次異步結果時
- 操作有明確的開始和結束時
- 不需要中間狀態(tài)更新時
- 簡單的異步操作
何時選擇 Stream
- 需要處理連續(xù)的數(shù)據(jù)序列時
- 需要實時更新和狀態(tài)變化時
- 需要取消和暫停機制時
- 復雜的事件處理場景
- 避免 Stream 過度使用:如果 Future 足夠,不要使用 Stream。
- 及時取消訂閱:防止內(nèi)存泄漏。
- 使用 StreamBuilder 優(yōu)化 UI。