|
|
|
@ -74,7 +74,7 @@ enum Poll<T> {
|
|
|
|
|
/// 之所以可以并发,是因为两个Future的轮询可以交替进行,一个阻塞,另一个就可以立刻执行,反之亦然
|
|
|
|
|
pub struct Join<FutureA, FutureB> {
|
|
|
|
|
// 结构体的每个字段都包含一个Future,可以运行直到完成.
|
|
|
|
|
// 如果Future完成后,字段会被设置为 `None`. 这样Future完成后,就不会再被轮询
|
|
|
|
|
// 等到Future完成后,字段会被设置为 `None`. 这样Future完成后,就不会再被轮询
|
|
|
|
|
a: Option<FutureA>,
|
|
|
|
|
b: Option<FutureB>,
|
|
|
|
|
}
|
|
|
|
@ -160,7 +160,7 @@ trait Future {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
首先这里多了一个 `Pin` ,关于它我们会在后面章节详细介绍,现在你只需要知道使用它可以创建一个无法被移动的 `Future` ,因为无法被移动,因此它将具有固定的内存地址,意味着我们可以存储它的指针(如果内存地址可能会变动,那存储指针地址将毫无意义!),也意味着可以实现一个自引用数据结构: `struct MyFut { a: i32, ptr_to_a: *const i32 }`。 而对于 `async/await` 来说,`Pin` 是不可或缺的关键特性。
|
|
|
|
|
首先这里多了一个 `Pin` ,关于它我们会在后面章节详细介绍,现在你只需要知道使用它可以创建一个无法被移动的 `Future` ,因为无法被移动,所以它将具有固定的内存地址,意味着我们可以存储它的指针(如果内存地址可能会变动,那存储指针地址将毫无意义!),也意味着可以实现一个自引用数据结构: `struct MyFut { a: i32, ptr_to_a: *const i32 }`。 而对于 `async/await` 来说,`Pin` 是不可或缺的关键特性。
|
|
|
|
|
|
|
|
|
|
其次,从 `wake: fn()` 变成了 `&mut Context<'_>` 。意味着 `wake` 函数可以携带数据了,为何要携带数据?考虑一个真实世界的场景,一个复杂应用例如 web 服务器可能有数千连接同时在线,那么同时就有数千 `Future` 在被同时管理着,如果不能携带数据,当一个 `Future` 调用 `wake` 后,执行器该如何知道是哪个 `Future` 调用了 `wake` ,然后进一步去 `poll` 对应的 `Future` ?没有办法!那之前的例子为啥就可以使用没有携带数据的 `wake` ? 因为足够简单,不存在歧义性。
|
|
|
|
|
|
|
|
|
@ -496,4 +496,4 @@ let event = io_blocker.block();
|
|
|
|
|
println!("Socket {:?} is now {:?}", event.id, event.signals);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这样,我们只需要一个执行器线程,它会接收 IO 事件并将其分发到对应的 `Waker` 中,接着后者会唤醒相关的任务,最终通过执行器 `poll` 后,任务可以顺利的继续执行, 这种 IO 读取流程可以不停的循环,直到 `socket` 关闭。
|
|
|
|
|
这样,我们只需要一个执行器线程,它会接收 IO 事件并将其分发到对应的 `Waker` 中,接着后者会唤醒相关的任务,最终通过执行器 `poll` 后,任务可以顺利地继续执行, 这种 IO 读取流程可以不停的循环,直到 `socket` 关闭。
|
|
|
|
|