简单原理
- 因为Dart并不是C语言, 所以对于线程来说, 它并不提供 用户级别的直接系统调用(线程库)
如C系列的语言(C, C++, OC等), 完全兼容C语言, 而内核又是由C语言编写, 内核提供了线程的C函数调用. <font color = green> 所以 当我们写 C程序时, 可以直接 系统调用内核的线程函数</font>
- 其他的语言要想实现线程调用, 则对应的编译器要提供 语法调用接口, 由编译器内部转化为系统调用
如 跨平台的 Java, 线程相关的的语法是Java语法, 但事实上 编译器 会将这些代码对接到系统调用, 也就是常说的 ABI
再如 js, js引擎(也就是编译器), 也提供了 线程相关的调用, 用户写的js异步操作, 内部会被js引擎 对接当前系统的 ABI
再如 swift, 也是一样的, 总之只要不是C系列的语言, 就不能由用户直接系统调用, 都要由编译器来完成
这里的Dart也是一样的道理, Dart是由google编写, 事实上Dart编译器 基本是对JS引擎的作了 很大优化, 这一点从语法结构来看2者有很大相似的地方
-
同样的 Dart的异步也是同JS一样, 有<font color = red>promise->async--await</font> 机制, 只不过名字不同, 语法稍微不同
ps: 从中可以看到 JS并没有文件IO相关的操作, 因为 JS编译器没有提供文件IO相关的 调用语法接口
-
还有一点要说, 就是关于网上说的
- JS是单线程
- Dart是单线程
这里直接说结论:
-
Dart运行 main.dart程序时, 事实上至少2个线程
- Dart运行环境所在的线程
- 代码
main函数
所在的线程就是<font color = red>所谓的主线程</font>
所谓的异步不管在JS或Dart中处理机制都是:
将任务添加到main线程所在的任何队列, 等当前主线程所有的任务执行完后,再从队列中取出任务继续执行
. 有一点可以验证:<font color = red>Dart环境和main函数所在线程不一样</font>
#include<unistd.h>
#include<iostream>
#include<pthread.h>
void* sub_thread(void* args){
sleep(1);
printf("sub_thread\n");
return nullptr;
}
int main(int args, char** argv){
printf("main_start\n");
pthread_t p;
pthread_create(&p, nullptr, sub_thread, nullptr);
printf("main_end\n");
return 0;
}
上述程序可以验证main函数结束后, 整个进程就结束了, 所以所谓的子线程不会有任何输出, 同样的处理逻辑, 放在Dart中如下:
import 'dart:io';
void main(List<String> args) {
print("start");
Future(
() {
sleep(Duration(seconds: 1));
print("sub");
},
);
sleep(Duration(secods:5));
print("end");
}
这里 <font color = red>Future</font>指定了一个回调, 事实上Dart会将回调中的代码在主线程中执行, 最后打印的顺序是:
start
end # 5秒后输出
sub # 再等待1秒输出
exited ... # 最后结束
从中可以看出, Dart在执行
Future
时, 将任务添加到了主线程的任务队列, 然后等待5秒直接输出end
, 然后并未结束, 而再等待1秒, 再输出sub
上述Dart程序和C++唯一不同的是, C++中直接开启了线程, 而当前的Dart程序中, 用户无法开启新的线程, Future的回调会被编译器编译成, 直接在main函数的任务队列中添加该操作
所谓的Dart单线程和异步
- 其实指的是: <font color = red>编写的任何
Future
或定时器
的回调, 其实都是编译器将这个回调的代码添加到了主线程的任务队列, 所以从用户使用的这一角度来看, Dart就是单线程, 添加任务的过程并不是耗时操作. 只有当当前的任务完毕后, 再回头来处理刚添加的任务, 所以就这是所谓的异步, 即不阻塞当前线程</font>
缺点:
任务本质还是在主线程执行, 若任务是耗时的, 则也会阻塞主线程
, 如下现在案例:
import 'dart:io';
void main(List<String> args) {
print("start");
Future(() {
sleep(Duration(seconds: 6));
print("sub2");
});
Future.delayed(Duration(seconds: 3), () {
print("main exec");
return "hello";
});
print("end");
}
该程序目的:
异步执行一个耗时的操作
Future, 但同时希望3秒
后延迟执行代码delayed
, 最终确是:
start
end # 等待6秒后, 立即输出 sub2
sub2
main exec # 输出sub2时, 不行再等待3秒, 而是立即输出
### 究其原因是: 主线程:
## 1. 打印 start
## 2. 添加 睡眠6秒的 任务到 主线程的任务队列中
## 3. 添加 延迟3秒的 任务到 主线程的任务队列中
## 4. 打印 end
### 取出队列的任务 (6秒的睡眠任务), 并在主线程执行, 这6秒中主线程被阻塞
### 被阻塞的过程中, 即使3秒后了, 也不会执行延迟任务
### 5. 6秒时间到 在主线程打印 sub2
### 6. 继续取出队列中的任务, 是一个延迟任务, 此时距离第3步的添加 延迟任务 已经过去了6秒, Dart的处理是, 当前时间已经过去了3秒, 所以不再等待, 直接执行
上述案例中, 若延迟任务是7秒, 则在第6步后, Dart会先看第3步的时任务被添加的时间, 然后再等待1秒后, 执行延迟任务
所谓的Future
简述
- 对应JS中的 <font color = green size = 4>Promise</font>, 也对应 <font color = red size = 4>C++标准线程库中的Future</font>, 它们在原理上是一样的
- 在Dart中Future的本质是将任务添加到主线程的任务队列, 然后等待当前所有未执行完毕的任务, 所以现象就是所谓的异步(
不阻塞main当前正在处理的任务
)
基本使用
- 结构
abstract interface class Future<T> {
...
factory Future(FutureOr<T> computation()) {
_Future<T> result = new _Future<T>();
Timer.run(() {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
});
return result;
}
...
}
上述 Future是一个抽象类, 但因为有了 factory, 所以也可以直接创建对象.
// 其中 fu 的类型就是 FutureOr<String> ()
var task = ()=> "hello";
// 将 task 丢给 Future, 实际内部返回的是 非公开的 _Future这个实现类
var fu = Future(task);
/**
* 内部会直接以异步的形式 将 task执行, 并将task的返回值传递给 _Future处理
1. Timer.run
相当于异步将 task 添加到 主线程, 这样做会保证 主线程当前的任务不会被阻塞
2. result._complete(computation());
其中result就是 _Future, computation就是task
这里直接 computation() 执行了传递的任务, 并将task的返回值 "hello" 交给 _Future处理
所以 当单纯的调用完 Future(task)后, Dart内部在取到 task并执行后, 并未直接结束, 而是会做更加深入的操作
这些操作就是为 Promise的链式调用作准备的
*/
异步操作的返回值
- 上述简单的Dart使用中, 用户其实关心的是
task
的返回值"hello"
. 但因为task的回调是所谓的异步(<font color = green>本质是当前main函数所在的线程执行完所有的任务后再回来取出它来执行</font>), 所以从逻辑上来说, 并不能确定什么时候被回调, 唯一能做的是接着在 task的函数中继续执行相关的业务逻辑
var task = (){
next_1("hello");
};
var fu = Future(task);
void next_1(value1){
next_2(value1);
}
void next_2(value2){
print("over$value");
}
上述的代码没有什么问题, 因为事实上, 只发生了一次异步(
task
), 但开发中可能遇到这种场景:用户的3个操作有严格的顺序依赖, 输入输出依赖
场景:
合成图层
有3个图层, layer1, layer2, layer3
现要求合成3个图层为layer, 并且顺序是 layer1-->layer2-->layer3
因为合成图层的操作比较耗时, 以当前Dart环境来看, 应该在将每个合成的操作 放在Future中, 类似的代码如下
var layer1 = "layer1";
void main(){
// 耗时操作, 会阻塞主线程
String render_layer(String layer, String something) {
return layer + something;
}
Future(() {
var new_layer1 = render_layer(layer1, "-1");
Future(() {
var new_layer2 = render_layer(new_layer1, "-2");
Future(() {
var new_layer3 = render_layer(new_layer2, "-3");
print(new_layer3);
});
});
});
}
这种实现在逻辑上并没有什么问题, 但出现了 <font color = red>嵌套地狱</font>, 具体就不解释了. 所以Dart中提供了改变这种情况的机制:
Future的链式调用
.这里所说的Future直接理解为JS中的Promise. 先来看一下链式的效果
Future(() => render_layer(layer1, "-1"))
.then((value) => render_layer(value, "-2"))
.then((value) => render_layer(value, "-3"))
.then((value) => print(value));
相比于前面的, 这个可读改更高. 现在来说一下它实现的原理.
前面说过当调用完 Future(task)后, 在回调task时, 将task的返回值传递给了 _Future, 它后续会做更细致的处理, 这里就不看源码了, 比较简单自己可以看, 直接说步骤:
1. 将task的返回值 "hello" 传递给 _Future
result._complete(computation()); result --> _Future, computation是task
2. _Future内部存储 task的返回值 task_result, 并且通知所有的监听者
_complete方法中:
1. _setValue(value as dynamic); 存储task_result
2. _propagateToListeners(this, listeners); 通知所有的监听者
3. 后续 用户通过 Future(task)的返回值 my_future对象调用 then方法时, Dart内部会做相应的处理, 并返回一个新的 _Future, 即调用完then后, 返回
了一个新的 _Future, 它的细节如下
3.1 then方法的调用如下
my_future.then((String value){
return 100;
});
3.2 then方法的声明如下
Future<R> then<R>(FutureOr<R> f(T value), {Function? onError});
从声明可以看出:
它返回一个新的类型 Future<R>
一个是必传参数 FutureOr<R> (T)
另一个可选参数
并且 参数f是方法, 它的返回类型 必须为 then方法返回的Future函数对象的返回值
如 3.1中的调用then时: 返回的是 Future<int>, 参数是 (String value){ return 100;}
所以 then调用后返回了 Future对象, 则可以继续链式调用
3.3 then方法的实现
Future<R> then<R>(FutureOr<R> f(T value), {Function? onError}) {
Zone currentZone = Zone.current;
...
_Future<R> result = new _Future<R>();
_addListener(new _FutureListener<T, R>.then(result, f, onError));
...
return result;
}
以上是抽取出来的代码, 主要操作是:
1. 将 新建 _Future<R> 对象, 这个R的类型就是 3.1调用时, 返回的int类型
2. 调用 _addListener, 该方法内部会 调用参数 f(), 即调用3.1中传递的 (String value){return 100;} 回调方法, 所以用户then传递的回调在这里执行
其中的value(_FutureListener的relust)在调用时被传递出来, 这个值其实就是 "hello". 与此同时拿到返回值100,存储在了 _Future<R>中, 等待下一次的then调用,再将其传递出来
3. 将 _Future<R>作为then的返回值给外界
ps: 第3步后, 用户得到了一个新的 Future<int>, 此时又可以再继续调用 then, 然后内部回调then的参数, 将100作为新的参数传递出来, 再拿到返回值存储在构造新的
_Future<R>中, 以此循环
Promise出错时回调的原理
- 上述过程描述了Promise被正确执行的过程, 若中间出错, 在Dart中的then的第2个参数就能用上了
Future<R> then<R>(FutureOr<R> f(T value), {Function? onError});
这里直接说结论: <font color = red>当前then回调抛出的异常, 会在后面出现的enError回调中执行</font>
Future(() {
return 100;
}).then((value) {
print(value);
throw Exception("hello");
}, onError: (msg, name) {
print(msg);
print("1");
}).then((value) { // __code_then_2
print(value);
}).then((value) {
print(value);
}, onError: (msg, name) {
print(msg);
print("3"); //__code_err_3
});
// 会在 __code_err_3中被捕获到
// 若 __code_then_2定义了 onError的回调, 则异常会在这里被捕获到
// 原因是: 源码中的 _propagateToListeners 方法负责执行当前 then和onError的回调
// 当执行 then的第1个用户的回调时, 若产生了异常会记录下来, 若后续 继续调用then, 同时传递了onError回调
// 会根据 记录下来的异常类型, 来匹配 当前遍历到的 onEorror, 匹配到了则直接抛出
// 结论就是: 当前then第1个 回调中抛出的异常, 不会在第2个参数的 onError中被捕获
// 会在后续出现的then的 onError作类型匹配, 被条件捕获出来
// 这个具体自己可以验证
CatchError
- 除了单独通过
then的第2参数用于捕获异常
, 也可以通过链式调用一个catchError(Error){}
的形式来捕获, 它的原理是一样的, 内部根据_propagateToListeners
所记录的异常标识, 来主动调用所提供的回调. 一般将它写在所有then调用的最后
Future(() {
return 100;
}).then((value) {
print(value);
throw Exception("hello");
}, onError: (msg, name) {
print(msg);
print("1");
}).then((value) {
print(value);
}).then((value) {
print(value);
}).catchError((msg, name) {
print(msg);
print("4");
});
await-sync
- 这个是编译器进一步优化, 允许用户
从上往下写代码
void main(List<String> args) {
var task = (String s, String other) { // __code_task_future
return Future(() => s + other);
};
() async { // __code_async
var r1 = await task("str", "+1");
var r2 = await task(r1, "+2");
var r3 = await task(r2, "+3");
print(r3);
}();
}
原理是编译器在编译时, 遇到
async
的函数时, 并不会做异步处理(意味着会阻塞当前线程
), 并且会做信号同步等待该函数中所有的await调用, await所调用的函数必须返回Future. 底层的原理本人在c++线程的探究中曾经看过VS2019实现的标准库Future, 在C++的实现Future内部会做锁同步机制, 所以也可以实现从上往下写代码
. 这里Dart环境中的原理在底层实现也是一样的
这里解释一下
async
函数本身是阻塞主线程的
import 'dart:async';
import 'dart:io';
typedef FutureOr<String> Fn();
var task = (String s, String other) {
return Future(() {
sleep(Duration(seconds: 2));
return s + other;
});
};
void fn() async {
print("async");
sleep(Duration(seconds: 5));
var r1 = await task("str", "+1");
var r2 = await task(r1, "+2");
var r3 = await task(r2, "+3");
print(r3);
}
void test() {
print("test-start");
fn();
print("test-end");
}
void main(List<String> args) {
test();
print("over");
}
该案例会在 async函数中阻塞主线程5秒, 但经过 await的task调用后, 会立即返回async函数, 直接先打印
test-end和over
, 也就是说, 当await的调用的Future中有超时等待, 则Dart会直接让原来的Async函数返回, 最后再按顺序处理所有的Future的返回, 这一点和C++中是不一样的