2.2.Tasks
这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。
原文链接:book.async.rs/
Tasks
既然已经了解了 Futures 是怎么一回事儿,下面一起来运行一下它们!
在 async-std
中, 模块 [tasks
][tasks] 起此作用。 最简单的方法是使用 block_on
函数:
# extern crate async_std;
use async_std::{fs::File, io, prelude::*, task};
// 读文件 异步方法
async fn read_file(path: &str) -> io::Result<String> {
let mut file = File::open(path).await?;
let mut contents = String::new();
file.read_to_string(&mut contents).await?;
Ok(contents)
}
fn main() {
let reader_task = task::spawn(async {
let result = read_file("data.csv").await;
match result {
Ok(s) => println!("{}", s),
Err(e) => println!("Error reading file: {:?}", e)
}
});
println!("Started task!");
task::block_on(reader_task);
println!("Stopped task!");
}
上述代码的逻辑是将读文件的代码托管给 async_std
运行时去执行。让我们从内到外的一步步深入执行逻辑。
# extern crate async_std;
# use async_std::{fs::File, io, prelude::*, task};
#
# async fn read_file(path: &str) -> io::Result<String> {
# let mut file = File::open(path).await?;
# let mut contents = String::new();
# file.read_to_string(&mut contents).await?;
# Ok(contents)
# }
#
async {
let result = read_file("data.csv").await;
match result {
Ok(s) => println!("{}", s),
Err(e) => println!("Error reading file: {:?}", e)
}
};
这是一个 async
代码块。调用 async
函数就少不了 async
代码块,并且 async
代码块向编译器标识在执行时需要包含进入执行过程的所有相关的指令。在 Rust 中,所有的代码块都要有返回值,而 async
代码块返回的值的类型是 Future
。
接下来,让我们开启有趣的部分:
# extern crate async_std;
# use async_std::task;
task::spawn(async { });
spawn
使用了 Future
,开启一个 Task
运行程序,返回 JoinHandle
。Rust 中的 Futures 有时被称作 冷 Futures。 意思是你需要一些辅助信息来帮助运行。为了运行 Future,可能需要一些额外的簿记,例如它的运行状态是正在运行还是已完成,它在内存中的位置,以及当前状态是什么。该簿记部分在 Task
中被抽象。
Task
和 Thread
相似,但又有一些细微差别:Task
由程序调度,而不是操作系统内核调度,并且在需要等待的地方,等待结束后程序本身负责再次唤醒它。这点稍后再讨论。一个 async_std
任务也可以有名称和 ID ,就像线程一样。
现在,只需要知道 spawn
ed 一个任务,该任务就可以继续在后台运行。JoinHandle
本身就是一个 future ,一旦 Task
结束,它就会结束。与 threads
和 join
函数非常相似,我们现在可以通过句柄调用 block_on
来 阻塞程序 (或者具体的说另启动一个线程) ,然后等待它完成。
async_std
中的 Task
Task 是 async_std
的核心抽象之一。 和 Rust 的 thread
一样,Task 提供了一些原始概念上的实用功能。 Task
与运行时有关, 同时它们本身又是独立的。 async_std
的 task 有许多理想的属性:
- 所有分配一次完成
- 所有任务都有 backchannel(回发通道),通过
JoinHandle
将结果和错误回传到生成任务 - 带有用于调试的元数据
- 支持本地存储任务
async_std
的 task API 可以处理后台运行时的设置和拆除,不用依赖于显式启动的运行时。
阻塞
假定 Task
是并发运行,那么可能是通过共享执行线程来处理并发的。这意味着阻塞进行中的系统线程的操作,例如
std::thread::sleep
或调用 Rust 的 std
类库的 io 函数,都将停止执行共享此线程的所有任务。其他的库(例如数据库驱动程序)就有类似的行为。 需注意,阻塞当前线程本身并不是不好的行为,只是其不能与 async-std
的并发执行模型不能很好地混合使用。本质上,永远不要有以下操作:
# extern crate async_std;
# use async_std::task;
fn main() {
task::block_on(async {
// this is std::fs, which blocks
std::fs::read_to_string("test_file");
})
}
如果要多种混合操作,请考虑将此类阻塞操作放在单独的 thread
上。
Errors and panics
Tasks report errors through normal patterns: If they are fallible, their Output
should be of kind Result<T,E>
.
In case of panic
, behaviour differs depending on whether there's a reasonable part that addresses the panic
. If not, the program aborts.
In practice, that means that block_on
propagates panics to the blocking component:
# extern crate async_std;
# use async_std::task;
fn main() {
task::block_on(async {
panic!("test");
});
}
thread 'async-task-driver' panicked at 'test', examples/panic.rs:8:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
While panicing a spawned task will abort:
# extern crate async_std;
# use async_std::task;
# use std::time::Duration;
task::spawn(async {
panic!("test");
});
task::block_on(async {
task::sleep(Duration::from_millis(10000)).await;
})
thread 'async-task-driver' panicked at 'test', examples/panic.rs:8:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
Aborted (core dumped)
That might seem odd at first, but the other option would be to silently ignore panics in spawned tasks. The current behaviour can be changed by catching panics in the spawned task and reacting with custom behaviour. This gives users the choice of panic handling strategy.
结论
async_std
附带了一个有用的 Task
类型,它与类似 std::thread
的 API 配合使用。它以结构化和定义的方式涵盖了错误行为和紧急情况。
任务是独立的并发单元,有时它们需要通信。这就是 Stream
的来源。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。