- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
在像C#这样的语言中,给出以下代码(我不是故意使用await
关键字):
async Task Foo()
{
var task = LongRunningOperationAsync();
// Some other non-related operation
AnotherOperation();
result = task.Result;
}
Task
(即将来的代码)。然后,您可以执行将与第一个操作并行运行的另一项操作,最后,您可以等待该操作完成。我认为这也是python,JavaScript等中
async
/
await
的行为。
A fundamental difference between Rust's futures and those from other languages is that Rust's futures do not do anything unless polled. The whole system is built around this: for example, cancellation is dropping the future for precisely this reason. In contrast, in other languages, calling an async fn spins up a future that starts executing immediately.
async
/
await
的目的是什么?从其他语言来看,这种表示法是运行并行操作的便捷方式,但是如果调用
async
函数不执行任何操作,则我看不到它在Rust中的工作方式。
最佳答案
您正在混淆一些概念。
Concurrency is not parallelism和async
和await
是用于并发的工具,这有时可能意味着它们也是并行性的工具。
此外,是否立即轮询将来与所选择的语法正交。async
/await
存在关键字async
和await
是为了使创建和与异步代码进行交互更容易阅读,并且看起来更像是“常规”同步代码。据我所知,在所有具有此类关键字的语言中都是如此。
更简单的代码
这是创建将来在轮询时将两个数字相加的代码
之前的
fn long_running_operation(a: u8, b: u8) -> impl Future<Output = u8> {
struct Value(u8, u8);
impl Future for Value {
type Output = u8;
fn poll(self: Pin<&mut Self>, _ctx: &mut Context) -> Poll<Self::Output> {
Poll::Ready(self.0 + self.1)
}
}
Value(a, b)
}
async fn long_running_operation(a: u8, b: u8) -> u8 {
a + b
}
poll_fn
functionasync
/await
可能令人惊讶的一件事是,它启用了以前无法实现的特定模式:在 future 中使用引用。以下是一些以异步方式用值填充缓冲区的代码:use std::io;
fn fill_up<'a>(buf: &'a mut [u8]) -> impl Future<Output = io::Result<usize>> + 'a {
futures::future::lazy(move |_| {
for b in buf.iter_mut() { *b = 42 }
Ok(buf.len())
})
}
fn foo() -> impl Future<Output = Vec<u8>> {
let mut data = vec![0; 8];
fill_up(&mut data).map(|_| data)
}
error[E0597]: `data` does not live long enough
--> src/main.rs:33:17
|
33 | fill_up_old(&mut data).map(|_| data)
| ^^^^^^^^^ borrowed value does not live long enough
34 | }
| - `data` dropped here while still borrowed
|
= note: borrowed value must be valid for the static lifetime...
error[E0505]: cannot move out of `data` because it is borrowed
--> src/main.rs:33:32
|
33 | fill_up_old(&mut data).map(|_| data)
| --------- ^^^ ---- move occurs due to use in closure
| | |
| | move out of `data` occurs here
| borrow of `data` occurs here
|
= note: borrowed value must be valid for the static lifetime...
use std::io;
async fn fill_up(buf: &mut [u8]) -> io::Result<usize> {
for b in buf.iter_mut() { *b = 42 }
Ok(buf.len())
}
async fn foo() -> Vec<u8> {
let mut data = vec![0; 8];
fill_up(&mut data).await.expect("IO failed");
data
}
async
函数不会运行任何东西Future
的实现和设计以及围绕 future 的整个系统与关键字async
和await
无关。确实,在async
/await
关键字出现之前,Rust拥有一个蓬勃发展的异步生态系统(例如Tokio)。 JavaScript也是如此。Future
进行轮询?A fundamental difference between Rust's futures and those from other languages is that Rust's futures do not do anything unless polled. The whole system is built around this: for example, cancellation is dropping the future for precisely this reason. In contrast, in other languages, calling an async fn spins up a future that starts executing immediately.
A point about this is that async & await in Rust are not inherently concurrent constructions. If you have a program that only uses async & await and no concurrency primitives, the code in your program will execute in a defined, statically known, linear order. Obviously, most programs will use some kind of concurrency to schedule multiple, concurrent tasks on the event loop, but they don't have to. What this means is that you can - trivially - locally guarantee the ordering of certain events, even if there is nonblocking IO performed in between them that you want to be asynchronous with some larger set of nonlocal events (e.g. you can strictly control ordering of events inside of a request handler, while being concurrent with many other request handlers, even on two sides of an await point).
This property gives Rust's async/await syntax the kind of local reasoning & low-level control that makes Rust what it is. Running up to the first await point would not inherently violate that - you'd still know when the code executed, it would just execute in two different places depending on whether it came before or after an await. However, I think the decision made by other languages to start executing immediately largely stems from their systems which immediately schedule a task concurrently when you call an async fn (for example, that's the impression of the underlying problem I got from the Dart 2.0 document).
Hi, I'm on the Dart team. Dart's async/await was designed mainly by Erik Meijer, who also worked on async/await for C#. In C#, async/await is synchronous to the first await. For Dart, Erik and others felt that C#'s model was too confusing and instead specified that an async function always yields once before executing any code.
At the time, I and another on my team were tasked with being the guinea pigs to try out the new in-progress syntax and semantics in our package manager. Based on that experience, we felt async functions should run synchronously to the first await. Our arguments were mostly:
Always yielding once incurs a performance penalty for no good reason. In most cases, this doesn't matter, but in some it really does. Even in cases where you can live with it, it's a drag to bleed a little perf everywhere.
Always yielding means certain patterns cannot be implemented using async/await. In particular, it's really common to have code like (pseudo-code here):
getThingFromNetwork():
if (downloadAlreadyInProgress):
return cachedFuture
cachedFuture = startDownload()
return cachedFutureIn other words, you have an async operation that you can call multiple times before it completes. Later calls use the same previously-created pending future. You want to ensure you don't start the operation multiple times. That means you need to synchronously check the cache before starting the operation.
If async functions are async from the start, the above function can't use async/await.
We pleaded our case, but ultimately the language designers stuck with async-from-the-top. This was several years ago.
That turned out to be the wrong call. The performance cost is real enough that many users developed a mindset that "async functions are slow" and started avoiding using it even in cases where the perf hit was affordable. Worse, we see nasty concurrency bugs where people think they can do some synchronous work at the top of a function and are dismayed to discover they've created race conditions. Overall, it seems users do not naturally assume an async function yields before executing any code.
So, for Dart 2, we are now taking the very painful breaking change to change async functions to be synchronous to the first await and migrating all of our existing code through that transition. I'm glad we're making the change, but I really wish we'd done the right thing on day one.
I don't know if Rust's ownership and performance model place different constraints on you where being async from the top really is better, but from our experience, sync-to-the-first-await is clearly the better trade-off for Dart.
If you need code to execute immediately when a function is called rather than later on when the future is polled, you can write your function like this:
fn foo() -> impl Future<Item=Thing> {
println!("prints immediately");
async_block! {
println!("prints when the future is first polled");
await!(bar());
await!(baz())
}
}
use futures; // 0.3.1
async fn long_running_operation(a: u8, b: u8) -> u8 {
println!("long_running_operation");
a + b
}
fn another_operation(c: u8, d: u8) -> u8 {
println!("another_operation");
c * d
}
async fn foo() -> u8 {
println!("foo");
let sum = long_running_operation(1, 2);
another_operation(3, 4);
sum.await
}
fn main() {
let task = foo();
futures::executor::block_on(async {
let v = task.await;
println!("Result: {}", v);
});
}
foo
,那么Rust中的事件顺序将是:Future<Output = u8>
的内容。 foo
的结果并将其逼近完成(通过对其进行轮询(在这种情况下,通过futures::executor::block_on
进行轮询)),那么接下来的步骤是:Future<Output = u8>
会返回实现long_running_operation
的内容(它尚未开始工作)。 another_operation
确实可以工作,因为它是同步的。 .await
语法使long_running_operation
中的代码开始。 foo
future将继续返回“未就绪”,直到计算完成。 foo
another_operation
long_running_operation
Result: 3
async
块async
块:use futures::{future, FutureExt}; // 0.3.1
fn long_running_operation(a: u8, b: u8) -> u8 {
println!("long_running_operation");
a + b
}
fn another_operation(c: u8, d: u8) -> u8 {
println!("another_operation");
c * d
}
async fn foo() -> u8 {
println!("foo");
let sum = async { long_running_operation(1, 2) };
let oth = async { another_operation(3, 4) };
let both = future::join(sum, oth).map(|(sum, _)| sum);
both.await
}
async
块中,然后等待两个操作完成,然后此功能才能完成。// Requires the `thread-pool` feature to be enabled
use futures::{executor::ThreadPool, future, task::SpawnExt, FutureExt};
async fn foo(pool: &mut ThreadPool) -> u8 {
println!("foo");
let sum = pool
.spawn_with_handle(async { long_running_operation(1, 2) })
.unwrap();
let oth = pool
.spawn_with_handle(async { another_operation(3, 4) })
.unwrap();
let both = future::join(sum, oth).map(|(sum, _)| sum);
both.await
}
关于asynchronous - Rust中异步/等待的目的是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57151795/
我正致力于通过 OAuth 合并外部 API,但对 expires_in 属性的用途有点迷惑。通过阅读,应该对 api token 的使用进行防御性编码,因为您应该预料到 token 在任何时候都可能
有人可以概述或总结一下 Spring 框架上下文中 bean 的用途吗? 我了解标准的 Java bean(没有 arg 构造函数、getter/setter,通常是序列化的),但 Spring be
使用 OpenGL 4.1 和 ARB_separate_shader_objects,我们能够在着色器程序中存储着色管道的不同阶段。众所周知,要使用这些,我们需要将它们附加到程序管道对象,然后绑定(
正如我从文档中了解到的那样,“MoveIteratorFactory”的目的是生成每一步都需要执行的 Action 。 “getSize”方法的移动子集有多大? “createOriginalMove
请解释 CMakeLists.txt 中这一行的目的是什么: 包括(InstallRequiredSystemLibraries) 我在 CMake 示例中看到这一行,但找不到好的解释,为什么我需要它
这里是新手。我仍在尝试理解在多个布局中运行单个进程或目的的概念。 例如,我想在我的申请中添加“提交后”功能。有一个包含标题、内容等文本框的主布局,以及一个链接到另一个布局以选择类别的按钮。我的问题是,
我在看 Box Oauth2.0 View Controller : https://github.com/box/box-ios-sdk-v2/blob/master/BoxSDK/OAuth2/B
我编写了一个将字符串复制到系统剪贴板的 Java 应用程序。构造函数使用 Clipboard.setContents(Transferable contents, ClipboardOwner own
阅读此文后:http://sourcemaking.com/design_patterns/command 我还是不太明白为什么我们需要这个。 最佳答案 想法是,如果命令被封装为对象,那么这些命令可以
我知道 c++ 中的模板是做什么的,但是今天我看到了一些奇怪的代码: template <> void swap(foo &a, foo &b) { a.name = b.name; a.
我不太明白 C# Collections 中 IEnumerator 的用途是什么。它的用途是什么,为什么要使用它? 我试着在线查看 http://msdn.microsoft.com/en-us/l
不幸的是,我今天做了一些代码考古(同时重构了一些旧的危险代码)并发现了这样的小化石: # line 7 "foo.y" 能在里面找到如此古老的宝藏,我完全惊呆了。我在 C 编程的网站上阅读了它。然而,
您能否澄清一下此注释的实际用途? - 如果我们没有使用数据库中的 SQL 表定义定义相应的约束,会发生什么情况。当我们尝试插入时,hibernate 会检查唯一性吗?或者这就是DB的目的吗?如果 hi
我在视频教程中看到过这段代码: const navToggle = ["Menu"].join(""); $(".site-header").prepend(navToggle); 我明白它的基本作用
我想知道这个成员函数的 scroll_to(TextBuffer::iterator& iter, double within_margin = 0)参数 within_margin。 API 是这样
我想知道是否可以将子目录提交到目录例如,假设您有 site.com/directory 可以将子目录提交到目录。我即将开始为希望她的网站在搜索引擎中排名靠前的客户进行一些搜索引擎优化。我知道实现此目的
STL 迭代器的用途是什么?为什么程序员要创造这个概念? 最佳答案 迭代器允许您将算法与容器分开。只要您有开始和结束迭代器,并且知道迭代器的功能(随机访问等),您就可以在迭代器指定的范围内进行操作。例
NSData *responseData = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:&respons
我正在编写代码,使用通用的 linux i2c 驱动程序 linux/i2c-dev.h 实现一个简单的 i2c 读/写功能 我对 ioctl 感到困惑:I2C_SLAVE 内核文档说明如下: You
在尝试克隆可变集合时,我最初的方法是对 mutable.Cloneable 特征使用 clone() 方法。但是,这取决于创建引用副本的 java.Object.clone 实现,而不是深拷贝。通过测
我是一名优秀的程序员,十分优秀!