热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

在Rust中async/await的目的是什么?

如何解决《在Rust中async/await的目的是什么?》经验,为你挑选了2个好方法。

在像C#这样的语言中,给出这个代码(我没有await故意使用关键字):

async Task Foo()
{
    var task = LongRunningOperationAsync();

    // Some other non-related operation
    AnotherOperation();

    result = task.Result;
}

在第一行中,long操作在另一个线程中运行,并Task返回a(即未来).然后,您可以执行另一个与第一个并行运行的操作,最后,您可以等待操作完成.我认为,这也是行为async/ await在Python,Javascript等

另一方面,在Rust中,我在RFC中读到:

Rust的期货与其他语言的期货之间的根本区别在于,除非进行调查,否则Rust的期货不会做任何事情.整个系统是围绕这个建立的:例如,取消正在降低未来正是出于这个原因.相比之下,在其他语言中,调用异步fn会旋转一个立即开始执行的未来.

在这种情况下,是什么目的async/ await鲁斯特?看到其他语言,这种表示法是一种运行并行操作的便捷方式,但是如果调用async函数没有运行任何东西,我无法看到它在Rust中是如何工作的.



1> Shepmaster..:

你在混淆一些概念.

并发不能并行,与asyncawait对于工具的并发性,这可能有时意味着它们也是并行的工具.

另外,是否立即轮询未来与所选语法正交.

async/await

关键字asyncawait存在使得创建和与异步代码交互更容易阅读,看起来更像"普通"同步代码.据我所知,在所有具有此类关键字的语言中都是如此.

更简单的代码

这是创建未来的代码,在轮询时添加两个数字

之前

fn long_running_operation(a: u8, b: u8) -> impl Future {
    struct Value(u8, u8);

    impl Future for Value {
        type Output = u8;

        fn poll(self: Pin<&mut Self>, _ctx: &mut Context) -> Poll {
            Poll::Ready(self.0 + self.1)
        }
    }

    Value(a, b)
}

async fn long_running_operation(a: u8, b: u8) -> u8 {
    a + b
}

请注意,"之前"代码基本上是今天功能的实现poll_fn

另见彼得霍尔关于如何更好地跟踪许多变量的答案.

参考

关于async/的一个潜在的令人惊讶的事情await是它能够实现以前不可能的特定模式:使用期货中的参考.这是一些以异步方式填充缓冲区的代码:

之前

use std::io;

fn fill_up<'a>(buf: &'a mut [u8]) -> impl Future> + 'a {
    futures::future::lazy(move |_| {
        for b in buf.iter_mut() { *b = 42 }
        Ok(buf.len())
    })
}

fn foo() -> impl Future> {
    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 {
    for b in buf.iter_mut() { *b = 42 }
    Ok(buf.len())
}

async fn foo() -> Vec {
    let mut data = vec![0; 8];
    fill_up(&mut data).await.expect("IO failed");
    data
}

这有效!

调用async函数不会运行任何操作

Future另一方面,围绕期货的整个系统的实施和设计与关键字async和无关await.实际上,在async/ await关键字存在之前,Rust拥有蓬勃发展的异步生态系统(例如Tokio).Javascript也是如此.

为什么不Future立即对创作进行调查?

有关最权威的答案,请在RFC pull请求中查看来自无船的评论:

Rust的期货与其他语言的期货之间的根本区别在于,除非进行调查,否则Rust的期货不会做任何事情.整个系统是围绕这个建立的:例如,取消正在降低未来正是出于这个原因.相比之下,在其他语言中,调用异步fn会旋转一个立即开始执行的未来.

关于这一点的一点是,Rust中的async和await不是固有的并发构造.如果您的程序只使用async&await且没有并发原语,程序中的代码将以定义的,静态已知的线性顺序执行.显然,大多数程序将使用某种并发方式在事件循环上调度多个并发任务,但它们不必这样做.这意味着你可以 - 平凡地 - 在本地保证某些事件的排序,即使在它们之间执行了非阻塞IO,你希望与一些更大的非本地事件异步(例如,你可以严格控制事件的顺序)在请求处理程序内部,同时与许多其他请求处理程序并发,甚至在等待点的两侧).

这个属性为Rust的async/await语法提供了一种本地推理和低级控制,使Rust成为现实.运行到第一个等待点本身并不违反 - 你仍然知道代码执行的时间,它只会在两个不同的地方执行,具体取决于它是在await之前还是之后.但是,我认为其他语言立即开始执行的决定很大程度上源于他们的系统,当你调用异步fn时,它会立即同时安排任务(例如,这是我从Dart 2.0文档得到的潜在问题的印象) .

本次讨论涵盖了Dart 2.0的一些背景知识:

嗨,我是Dart团队的成员.Dart的async/await主要是由Erik Meijer设计的,他也在为异步/等待C#工作.在C#中,async/await与第一个await同步.对于Dart,Erik和其他人认为C#的模型太混乱,而是指定异步函数在执行任何代码之前总是产生一次.

当时,我和我的团队中的另一个人负责成为试验品,在我们的包管理器中尝试新的正在进行的语法和语义.根据这一经验,我们认为异步函数应该与第一个等待同步运行.我们的论点主要是:

    在没有充分理由的情况下,总是屈服于性能损失.在大多数情况下,这并不重要,但有些确实如此.即使在你可以忍受它的情况下,在任何地方流血也是一种拖累.

    总是屈服意味着使用async/await无法实现某些模式.特别是,像(伪代码)这样​​的代码是很常见的:

    getThingFromNetwork():
      if (downloadAlreadyInProgress):
        return cachedFuture
    
      cachedFuture = startDownload()
      return cachedFuture
    

    换句话说,您有一个异步操作,可以在完成之前多次调用.以后的呼叫使用相同的先前创建的待定未来.您希望确保不要多次启动操作.这意味着您需要在开始操作之前同步检查缓存.

    如果异步函数从一开始就是异步,则上述函数不能使用async/await.

我们恳求我们的案例,但最终语言设计师坚持使用async-from-the-top.这是几年前的事了.

结果证明是错误的电话.性能成本是足够的,许多用户开发了"异步功能很慢"的思维模式,即使在性价比很高的情况下也开始避免使用它.更糟糕的是,我们看到令人讨厌的并发错误,人们认为他们可以在一个函数的顶部做一些同步工作,并且发现他们已经创造了竞争条件而感到沮丧.总的来说,似乎用户在执行任何代码之前不会自然地假设异步函数产生.

因此,对于Dart 2,我们现在正在进行非常痛苦的重大改变,将异步函数更改为与第一个同步,并通过该转换迁移所有现有代码.我很高兴我们正在做出改变,但我真的希望我们在第一天做正确的事情.

我不知道Rust的所有权和性能模型是否会对你提出不同的限制,从顶部开始异步更好,但从我们的经验来看,同步到第一等待显然是Dart的更好的权衡.

cramert回复(请注意,此语法中的一些现已过时):

如果您需要在调用函数时立即执行代码而不是稍后在轮询未来时执行代码,您可以编写如下函数:

fn foo() -> impl Future {
    println!("prints immediately");
    async_block! {
        println!("prints when the future is first polled");
        await!(bar());
        await!(baz())
    }
}

代码示例

这些示例使用1.31.0-nightly(2018-10-15)中的异步支持和期货预览包(0.3.0-alpha.7).

C#代码的文字转录

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返回实现的东西.

而已.尚未完成"实际"工作.如果您获取结果foo并将其推向完成(通过轮询,在这种情况下通过futures::executor::block_on),则接下来的步骤是:

    Future从调用返回实现的东西long_running_operation(它还没有开始工作).

    another_operation 确实有效,因为它是同步的.

    .await宏导致的代码long_running_operation开始.在foo未来将继续返回"没有准备好",直到计算完成.

输出将是:

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块中,然后等待这两个操作完成,然后才能完成此功能.

请注意,对于任何实际需要很长时间的事情来说,包装这样的同步代码并不是一个好主意.请参阅在将来-rs中封装阻塞I/O的最佳方法是什么?了解更多信息.

有线程池

// 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
}


抱歉,目前还不清楚。您是否有一个Rust代码示例,其功能与我编写的C#代码相同?我的意思是:有2个与`async` /`await`异步运行的操作。

2> Peter Hall..:

考虑这个简单的伪Javascript代码,它获取一些数据,处理它,根据前一步获取更多数据,汇总它,然后打印结果:

getData(url)
   .then(response -> parseObjects(response.data))
   .then(data -> findAll(data, 'foo'))
   .then(foos -> getWikipediaPagesFor(foos))
   .then(sumPages)
   .then(sum -> console.log("sum is: ", sum));

async/await形式上,那是:

async {
    let respOnse= await getData(url);
    let objects = parseObjects(response.data);
    let foos = findAll(objects, 'foo');
    let pages = await getWikipediaPagesFor(foos);
    let sum = sumPages(pages);
    console.log("sum is: ", sum);
}

它引入了大量的一次性使用的变量,可以说是比原来的版本与承诺差.那为什么要这么麻烦?

考虑这个变化,变量responseobjects稍后在计算中需要:

async {
    let respOnse= await getData(url);
    let objects = parseObjects(response.data);
    let foos = findAll(objects, 'foo');
    let pages = await getWikipediaPagesFor(foos);
    let sum = sumPages(pages, objects.length);
    console.log("sum is: ", sum, " and status was: ", response.status);
}

并尝试使用promises以原始形式重写它:

getData(url)
   .then(response -> Promise.resolve(parseObjects(response.data))
       .then(objects -> Promise.resolve(findAll(objects, 'foo'))
           .then(foos -> getWikipediaPagesFor(foos))
           .then(pages -> sumPages(pages, objects.length)))
       .then(sum -> console.log("sum is: ", sum, " and status was: ", response.status)));

每次需要返回前一个结果时,都需要将整个结构嵌套一层.这很快就会变得非常难以阅读和维护,但async/ await版本不会遇到这个问题.


推荐阅读
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • IB 物理真题解析:比潜热、理想气体的应用
    本文是对2017年IB物理试卷paper 2中一道涉及比潜热、理想气体和功率的大题进行解析。题目涉及液氧蒸发成氧气的过程,讲解了液氧和氧气分子的结构以及蒸发后分子之间的作用力变化。同时,文章也给出了解题技巧,建议根据得分点的数量来合理分配答题时间。最后,文章提供了答案解析,标注了每个得分点的位置。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Python语法上的区别及注意事项
    本文介绍了Python2x和Python3x在语法上的区别,包括print语句的变化、除法运算结果的不同、raw_input函数的替代、class写法的变化等。同时还介绍了Python脚本的解释程序的指定方法,以及在不同版本的Python中如何执行脚本。对于想要学习Python的人来说,本文提供了一些注意事项和技巧。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • PHPMailer邮件类邮件发送功能的使用教学及注意事项
    本文介绍了使用国外开源码PHPMailer邮件类实现邮件发送功能的简单教学,同时提供了一些注意事项。文章涵盖了字符集设置、发送HTML格式邮件、群发邮件以及避免类的重定义等方面的内容。此外,还提供了一些与PHP相关的资源和服务,如传奇手游游戏源码下载、vscode字体调整、数据恢复、Ubuntu实验环境搭建、北京爬虫市场、进阶PHP和SEO人员需注意的内容。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文总结了在开发中使用gulp时的一些技巧,包括如何使用gulp.dest自动创建目录、如何使用gulp.src复制具名路径的文件以及保留文件夹路径的方法等。同时介绍了使用base选项和通配符来保留文件夹路径的技巧,并提到了解决带文件夹的复制问题的方法,即使用gulp-flatten插件。 ... [详细]
author-avatar
垚垚8858
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有