Tasks
Now that we know what Futures are, we want to run them!
既然我们知道Futures 是什么,我们就要运行它们!
In async-std
, the tasks
module is responsible for this. The simplest way is using the block_on
function:
在async_std中,tasks模块负责这一点。最简单的方法是使用block_on函数:
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!");
}
This asks the runtime baked into async_std to execute the code that reads a file. Let's go one by one, though, inside to outside.
这要求在async_std中嵌入运行时来执行读取文件的代码。让我们一个一个从里到外。
async {
let result = read_file("data.csv").await;
match result {
Ok(s) => println!("{}", s),
Err(e) => println!("Error reading file: {:?}", e)
}
};
This is an async block. Async blocks are necessary to call async functions, and will instruct the compiler to include all the relevant instructions to do so. In Rust, all blocks return a value and async blocks happen to return a value of the kind Future.
这是一个异步块。异步块是调用异步函数所必需的,它将指示编译器包含所有相关的指令。在Rust中,所有块都返回一个值,而异步块恰好返回一个未来类型的值。
But let's get to the interesting part:
但让我们来看看有趣的部分:
task::spawn(async { });
spawn takes a Future and starts running it on a Task. It returns a JoinHandle. Futures in Rust are sometimes called cold Futures. You need something that starts running them. To run a Future, there may be some additional bookkeeping required, e.g. whether it's running or finished, where it is being placed in memory and what the current state is. This bookkeeping part is abstracted away in a Task.
spawn需要一个Future 并开始在一个任务上运行它。它返回一个JoinHandle。Rust 的Futures 有时被称为冻结的Futures。你需要一些东西来管理和运行他们。为了运行一个Future,可能需要一些额外的记录请求,例如它是运行的还是完成的,它放在内存中的位置以及当前的状态。这个记录部分是在一个任务中抽象出来的。
A Task is similar to a Thread, with some minor differences: it will be scheduled by the program instead of the operating system kernel, and if it encounters a point where it needs to wait, the program itself is responsible for waking it up again. We'll talk a little bit about that later. An async_std task can also have a name and an ID, just like a thread.
任务类似于线程,但有一些细微的区别:任务将由程序而不是操作系统内核调度,如果遇到需要等待的点,程序本身将负责再次唤醒它。我们稍后再谈。异步任务也可以有名称和ID,就像线程一样。
For now, it is enough to know that once you have spawned a task, it will continue running in the background. The JoinHandle is itself a future that will finish once the Task has run to conclusion. Much like with threads and the join function, we can now call block_on on the handle to block the program (or the calling thread, to be specific) and wait for it to finish.
现在,只要知道一旦生成了一个任务,它将继续在后台运行就足够了。JoinHandle本身就是一个future ,它将在任务运行到结束时完成。与线程和join函数非常相似,我们现在可以调用句柄上的block_on阻止程序(或调用线程,具体来说)并等待它完成。
Tasks in async_std
Tasks in async_std are one of the core abstractions. Much like Rust's threads, they provide some practical functionality over the raw concept. Tasks have a relationship to the runtime, but they are in themselves separate. async_std tasks have a number of desirable properties:
异步标准中的任务是核心抽象之一。就像Rust的线程一样,它们提供了一些超越原始概念的实用功能。任务与运行时有关系,但它们本身是独立的。异步任务具有许多理想的属性:
- They are allocated in one single allocation //它们被分配到一个单独的分配中
- All tasks have a backchannel, which allows them to propagate results and errors to the spawning task through the JoinHandle //所有任务都有一个backchannel,允许它们通过JoinHandle将结果和错误传播到生成任务
- They carry useful metadata for debugging //它们为调试提供了有用的元数据
- They support task local storage //它们支持任务本地存储
async_stds task API handles setup and teardown of a backing runtime for you and doesn't rely on a runtime being explicitly started.
async_stds task API为您处理后台运行时的设置和拆卸,而不依赖于显式启动的运行时。
Blocking
Tasks are assumed to run concurrently, potentially by sharing a thread of execution. This means that operations blocking an operating system thread, such as std::thread::sleep or io function from Rust's std library will stop execution of all tasks sharing this thread. Other libraries (such as database drivers) have similar behaviour. Note that blocking the current thread is not in and of itself bad behaviour, just something that does not mix well with the concurrent execution model of async-std. Essentially, never do this:
假定任务是并发运行的,可能是通过共享一个执行线程来实现的。这意味着阻塞操作系统线程的操作,例如std::thread::sleep或来自Rust的std库的io函数,将停止共享该线程的所有任务的执行。其他库(如数据库驱动程序)也有类似的行为。注意,阻塞当前线程本身并不是不好的行为,只是与async-std的并发执行模型不能很好地结合在一起。基本上,永远不要这样做:
fn main() {
task::block_on(async {
// this is std::fs, which blocks
std::fs::read_to_string("test_file");
})
}
If you want to mix operation kinds, consider putting such blocking operations on a separate thread.
如果希望混合操作类型,请考虑将此类阻塞操作放在单独的线程上.
Errors and panics
Tasks report errors through normal patterns: If they are fallible, their Output should be of kind Result<T,E>.
任务通过正常模式报告错误:如果它们是错误的,那么它们的输出应该是类 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.
在 panic 的情况下,行为的不同取决于是否有一个合理的部分来解决panic。否则,程序将中止。
In practice, that means that block_on propagates panics to the blocking component:
实际上,这意味着block-on将 panics 传播到blocking组件:
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:
在 panicing, 生成任务将中止:
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)
//线程'async task driver'在'test'时崩溃,examples/panic.rs:8:9
//注意:使用“RUST\u BACKTRACE=1”环境变量运行以显示回溯。
//中止(核心转储)
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.
一开始这可能看起来很奇怪,但另一个操作是默默地忽略spawned tasks中的 panics 。当前的行为可以通过捕获spawned tasks中的panics 并对自定义行为作出反应来改变。这为用户提供了panics 处理策略的选择。
Conclusion 结论
async_std comes with a useful Task type that works with an API similar to std::thread. It covers error and panic behaviour in a structured and defined way.
async_std附带了一个有用的任务类型,它与类似std::thread的API一起工作。它以结构化和明确的方式涵盖了error 和 panic 行为。
Tasks are separate concurrent units and sometimes they need to communicate. That's where Streams come in.
任务是独立的并发单元,有时它们需要通信。那是 Streams 流入的地方。