Rust异步之Future
对异步的学习,我们先从Future开始,学习异步的实现原理。等理解了异步是怎么实现的后,再学习Rust异步编程涉及的2个库(futures、tokio)的时候就容易理解多了。
Future
rust中Future的定义如下,一个Future可以理解为一段供将来调度执行的代码。我们为什么需要异步呢,异步相比同步高效在哪里呢?就是异步环境下,当前调用就绪时则执行,没有就绪时则不等待任务就绪,而是返回一个Future,等待将来任务就绪时再调度执行。当然,这里返回Future时关键的是要声明事件什么时候就绪,就绪后怎么唤醒这个任务到调度器去调度执行。
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[lang = "future_trait"]
pub trait Future { // A future represents an asynchronous computation.
type Output;
/* The core method of future, poll, attempts to resolve the future into a final value. This method does not block if the value is not ready. Instead, the current task is scheduled to be woken up when it's possible to make further progress by polling again. */
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output>;
}
可以看到执行后的返回结果,一个是就绪返回执行结果,另一个是未就绪待定。
#[must_use = "this `Poll` may be a `Pending` variant, which should be handled"]
pub enum Poll<T> {
Ready(T),
Pending,
}
可能到这里你还是云里雾里,我们写一段代码,帮助你理解。完整代码见:future_study
use futures;
use std::{future::Future, pin::Pin, sync::{Arc, Mutex}, task::{Context, Poll, Waker}, thread, time::Duration};
fn main() {
// 我们现在还没有实现调度器,所以要用一下futues库里的一个调度器。
futures::executor::block_on(TimerFuture::new(Duration::new(10, 0)));
}
struct SharedState {
completed: bool,
waker: Option<Waker>,
}
// 我们想要实现一个定时器Future
pub struct TimerFuture {
share_state: Arc<Mutex<SharedState>>,
}
// impl Future trait for TimerFuture.
impl Future for TimerFuture {
type Output = ();
// executor will run this poll ,and Context is to tell future how to wakeup the task.
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut share_state = self.share_state.lock().unwrap();
if share_state.completed {
println!("future ready. execute poll to return.");
Poll::Ready(())
} else {
println!("future not ready, tell the future task how to wakeup to executor");
// 你要告诉future,当事件就绪后怎么唤醒任务去调度执行,而这个waker根具体的调度器有关
// 调度器执行的时候会将上下文信息传进来,里面最重要的一项就是Waker
share_state.waker = Some(cx.waker().clone());
Poll::Pending
}
}
}
impl TimerFuture {
pub fn new(duration: Duration) -> Self {
let share_state = Arc::new(Mutex::new(SharedState{completed:false, waker:None}));
let thread_shared_state = share_state.clone();
thread::spawn(move || {
thread::sleep(duration);
let mut share_state = thread_shared_state.lock().unwrap();
share_state.completed = true;
if let Some(waker) = share_state.waker.take() {
println!("detect future is ready, wakeup the future task to executor.");
waker.wake() // wakeup the future task to executor.
}
});
TimerFuture {share_state}
}
}
执行结果如下:
future not ready, tell the future task how to wakeup to executor
detect future is ready, wakeup the future task to executor.
future ready. execute poll to return.
可以看到,刚开始的时候,定时10s事件还未完成,处在Pending状态,这时要告诉这个任务后面就绪后怎么唤醒去调度执行。等10s后,定时事件完成了,通过前面的设置的Waker,唤醒这个Future任务去调度执行。这里,我们看一下Context和Waker是怎么定义的:
/// The `Context` of an asynchronous task.
///
/// Currently, `Context` only serves to provide access to a `&Waker`
/// which can be used to wake the current task.
#[stable(feature = "futures_api", since = "1.36.0")]
pub struct Context<'a> {
waker: &'a Waker,
// Ensure we future-proof against variance changes by forcing
// the lifetime to be invariant (argument-position lifetimes
// are contravariant while return-position lifetimes are
// covariant).
_marker: PhantomData<fn(&'a ()) -> &'a ()>,
}
// A Waker is a handle for waking up a task by notifying its executor that it is ready to be run.
#[repr(transparent)]
#[stable(feature = "futures_api", since = "1.36.0")]
pub struct Waker {
waker: RawWaker,
}
现在你应该对Future有新的理解了,上面的代码,我们并没有实现调度器,而是使用的futures库中提供的一个调度器去执行,下面自己实现一个调度器,看一下它的原理。而在Rust中,真正要用的话,还是要学习tokio库,这里我们只是为了讲述一下实现原理,以便于理解异步是怎么一回事。完整代码见:future_study, 关键代码如下:
use std::{future::Future, pin::Pin, sync::{Arc, Mutex}, task::{Context, Poll, Waker}, thread, time::Duration};
use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
use futures::{future::{FutureExt, BoxFuture}, task::{ArcWake, waker_ref}};
use super::timefuture::*;
pub fn run_executor() {
let (executor, spawner) = new_executor_and_spawner();
// 将Future封装成一个任务,分发到调度器去执行
spawner.spawn( async {
let v = TimerFuture::new(Duration::new(10, 0)).await;
println!("return value: {}", v);
v
});
drop(spawner);
executor.run();
}
fn new_executor_and_spawner() -> (Executor, Spawner) {
const MAX_QUEUE_TASKS: usize = 10_000;
let (task_sender, ready_queue) = sync_channel(MAX_QUEUE_TASKS);
(Executor{ready_queue}, Spawner{task_sender})
}
// executor , received ready task to execute.
struct Executor {
ready_queue: Receiver<Arc<Task>>,
}
impl Executor {
// 实际运行具体的Future任务,不断的接收Future task执行。
fn run(&self) {
let mut count = 0;
while let Ok(task) = self.ready_queue.recv() {
count = count + 1;
println!("received task. {}", count);
let mut future_slot = task.future.lock().unwrap();
if let Some(mut future) = future_slot.take() {
let waker = waker_ref(&task);
let context = &mut Context::from_waker(&*waker);
if let Poll::Pending = future.as_mut().poll(context) {
*future_slot = Some(future);
println!("executor run the future task, but is not ready, create a future again.");
} else {
println!("executor run the future task, is ready. the future task is done.");
}
}
}
}
}
// 负责将一个Future封装成一个Task,分发到调度器去执行。
#[derive(Clone)]
struct Spawner {
task_sender: SyncSender<Arc<Task>>,
}
impl Spawner {
// encapsul a future object to task , wakeup to executor.
fn spawn(&self, future: impl Future<Output = String> + 'static + Send) {
let future = future.boxed();
let task = Arc::new(Task {
future: Mutex::new(Some(future)),
task_sender: self.task_sender.clone(),
});
println!("first dispatch the future task to executor.");
self.task_sender.send(task).expect("too many tasks queued.");
}
}
// 等待调度执行的Future任务,这个任务必须要实现ArcWake,表明怎么去唤醒任务去调度执行。
struct Task {
future: Mutex<Option<BoxFuture<'static, String>>>,
task_sender: SyncSender<Arc<Task>>,
}
impl ArcWake for Task {
// A way of waking up a specific task.
fn wake_by_ref(arc_self: &Arc<Self>) {
let clone = arc_self.clone();
arc_self.task_sender.send(clone).expect("too many tasks queued");
}
}
运行结果如下:
first dispatch the future task to executor.
received task. 1
future not ready, tell the future task how to wakeup to executor
executor run the future task, but is not ready, create a future again.
detect future is ready, wakeup the future task to executor.
received task. 2
future ready. execute poll to return.
return value: timer done.
executor run the future task, is ready. the future task is done.
第一次调度的时候,因为还没有就绪,在Pending状态,告诉这个任务,后面就绪是怎么唤醒该任务。然后当事件就绪的时候,因为前面告诉了如何唤醒,按方法唤醒了该任务去调度执行。其实,在实际应用场景中,难的地方还在于,你怎么知道什么时候事件就绪,去唤醒任务,我们很容易联想到Linux系统的epoll,tokio等底层,也是基于epoll实现的。通过epoll,我们就能方便的知道事件什么时候就绪了。
参考资料
主要学习资料如下:
上面的文章主要是学习异步的实现原理,理解异步是怎么实现的,而进行Rust异步编程时的具体实现,则主要依赖下面2个库:
学习这两个库的时候,一定要注意版本问题,这两个库最近变化的比较快,一定要学最新的。
Rust异步之Future的更多相关文章
- Netty 中的异步编程 Future 和 Promise
Netty 中大量 I/O 操作都是异步执行,本篇博文来聊聊 Netty 中的异步编程. Java Future 提供的异步模型 JDK 5 引入了 Future 模式.Future 接口是 Java ...
- Tokio,Rust异步编程实践之路
缘起 在许多编程语言里,我们都非常乐于去研究在这个语言中所使用的异步网络编程的框架,比如说Python的 Gevent.asyncio,Nginx 和 OpenResty,Go 等,今年年初我开始接触 ...
- Java异步调用Future对象
Future类存在于JDK的concurrent包中,主要用途是接收Java的异步线程计算返回的结果. 个人理解的使用场景大概如下: 有两个任务A和B,A任务中仅仅需要使用B任务计算成果,有两种方法实 ...
- java异步计算Future的使用(转)
从jdk1.5开始我们可以利用Future来跟踪异步计算的结果.在此之前主线程要想获得工作线程(异步计算线程)的结果是比较麻烦的事情,需要我们进行特殊的程序结构设计,比较繁琐而且容易出错.有了Futu ...
- flutter中的异步机制Future
饿补一下Flutter中Http请求的异步操作. Dart是一个单线程语言,可以理解成物理线路中的串联,当其遇到有延迟的运算(比如IO操作.延时执行)时,线程中按顺序执行的运算就会阻塞,用户就会感觉到 ...
- 并发编程之Callable异步,Future模式
Callable 在Java中,创建线程一般有两种方式,一种是继承Thread类,一种是实现Runnable接口.然而,这两种方式的缺点是在线程任务执行结束后,无法获取执行结果.我们一般只能采用共享变 ...
- flutter中的异步机制 Future
饿补一下Flutter中Http请求的异步操作. Dart是一个单线程语言,可以理解成物理线路中的串联,当其遇到有延迟的运算(比如IO操作.延时执行)时,线程中按顺序执行的运算就会阻塞,用户就会感觉到 ...
- Flutter 的异步机制Future
Dart是一个单线程语言,可以理解成物理线路中的串联,当其遇到有延迟的运算(比如IO操作.延时执行)时,线程中按顺序执行的运算就会阻塞,用户就会感觉到卡顿,于是通常用异步处理来解决这个问题. Dart ...
- Dart异步编程-future
Dart异步编程包含两部分:Future和Stream 该篇文章中介绍Future 异步编程:Futures Dart是一个单线程编程语言.如果任何代码阻塞线程执行都会导致程序卡死.异步编程防止出现阻 ...
随机推荐
- MySQL数据库回表与索引
目录 回表的概念 1.stu_info表案例 2.查看刚刚建立的表结构 3.插入测试数据 4.分析过程 5.执行计划 回表的概念 先得出结论,根据下面的实验.如果我要获得['liu','25']这条记 ...
- GraphQL-- 使用Apollo Server搭建Node服务端
一.关于Apollo Server Apollo Server是一种使用JS创建GraphQL服务端的一个方案.它的兼容性比较好,可以很好地和GraphQL客户端进行兼容.同时它可以 独立作为服务端进 ...
- React组件setState
注意: 1. 自定义组件首字母必须大写.这里是以函数表达式的方式定义子组件的. 2. 使用 ES6 的 class 关键字创建的 React 组件,组件中的方法遵循与常规 ES6 class 相同的语 ...
- css3弹性布局
二.弹性布局(重点******************************************) 1.什么是弹性布局 弹性布局,是一种布局方式. 主要解决的是某个元素中子元素的布局方式 让页面 ...
- Django之请求生命周期
settings.py中间件执行 自定义中间件的配置: (1)任意新建一个py文件,导入模块from django.utils.deprecation import MiddlewareMixin ( ...
- 安装的SQL Server2008 R2版的连接不到本地数据,提示未找到或无法访问服务器。----复制自百度知道
安装的SQL Server2008 R2版的连接不到本地数据,提示未找到或无法访问服务器.使用Windows身份验证 2012-09-17 00:23hj168926 | 分类:数据库DB | 浏览3 ...
- GYM101635E Ingredients
题目链接:https://vjudge.net/problem/Gym-101635E 题目大意: 给定一个有 \(N\) 条边的有向无环图(有多个起点),每条边都有其费用和收益,现要从一个或多个起点 ...
- 实验三:Linux系统用户管理及VIM配置
项目 内容 这个作业属于哪个课程 班级课程的主页链接 这个作业的要求在哪里 作业要求链接地址 学号-姓名 17043133-木腾飞 学习目标 1.学习Linux系统用户管理2.学习vim使用及配置 实 ...
- 软件攻城狮究级装B指南
引言 装B于无形,随性而动,顺道而行,待霸业功成之时,你会发现:装B是牛B最好的的试金石. -- SuperDo 第一章.人间兵器(准备工具) <论语·魏灵公>:“工欲善其事,必先利其器. ...
- 离散的差分进化Discrete DE
一般的差分算法的变异规则:Xmutation=Xr1+F(Xr2-Xr3),F为缩放因子, 离散差分进化DDE的变异规则:设每个解为K个元素的集合,则Xr2-Xr3:求出Xr2与Xr3有m个共同元素, ...