Rust Async 异步编程(十):一些疑难问题的解决方法

Rust 2024介绍 | 开发环境搭建详细教程(rust 1.85.0) Rust 是一门以安全、高效、可靠为核心设计的系统级编程语言,凭借其独特的「零成本抽象」理念,在开发效率与运行时性能之间实现了罕见平衡。它不仅连续多年蝉联「开发者最喜爱语言」榜单,更被Linux内核团队选中作为C语言的补充,成为下一代系统开发的革新力量。cargo new: 新建一个rust项日cargo run: 直接运行代码: 只编译代码,不运行,默认编译的为 debug 版本: 编译为 release 版本同时还可以使用upx工具减小最终编译程序的体积。500佰欢迎佬留言+关注。 阅读详情

Rust Async 异步编程(十):一些疑难问题的解决方法

async 在 Rust 依然比较新,疑难杂症少不了,而它们往往还处于活跃开发状态,短时间内无法被解决。下面一起来看看这些问题以及相应的临时解决方案。

在 async 语句块中使用 ?

async 语句块和 async fn 最大的区别就是前者无法显式的声明返回值,在大多数时候这都不是问题,但是当配合 ? 一起使用时,问题就有所不同:

async fn foo() -> Result<u8, String> {
    Ok(1)
}
async fn bar() -> Result<u8, String> {
    Ok(1)
}
pub fn main() {
    let fut = async {
        foo().await?;
        bar().await?;
        Ok(())
    };
}

以上代码编译后会报错:

error[E0282]: type annotations needed
  --> src/main.rs:14:9
   |
11 |     let fut = async {
   |         --- consider giving `fut` a type
...
14 |         Ok(1)
   |         ^^ cannot infer type for type parameter `E` declared on the enum `Result`

原因在于编译器无法推断出 Result<T, E> 中的 E 的类型, 而且编译器的提示 consider giving `fut` a type 你也别傻乎乎的相信,然后尝试半天,最后无奈放弃:目前还没有办法为 async 语句块指定返回类型。

既然编译器无法推断出类型,那咱就给它更多提示,可以使用 ::< … > 的方式来增加类型注释:

let fut = async {
    foo().await?;
    bar().await?;
    Ok::<(), String>(()) // 在这一行进行显式的类型注释
};

给予类型注释后此时编译器就知道Result<T, E>中的 E 的类型是String,进而成功通过编译。

async 函数和 Send trait

Send trait 对于多线程间数据传递的重要性,对于 async fn 也是如此,它返回的 Future 能否在线程间传递的关键在于 .await 运行过程中,作用域中的变量类型是否是 Send。

当值可能在跨越 .await 点被持有时,编译器会尽力近似估算。但这种估算在很多地方显得过于保守。

学到这里,相信大家已经很清楚 Rc 无法在多线程环境使用,原因就在于它并未实现 Send trait,那咱就用它来做例子:

use std::rc::Rc;

#[derive(Default)]
struct NotSend(Rc<()>);

事实上,未实现 Send trait 的变量可以出现在 async fn 语句块中:

async fn bar() {}
async fn foo() {
    NotSend::default();
    bar().await;
}

fn require_send(_: impl Send) {}

fn main() {
    require_send(foo());
}

即使上面的 foo 返回的 Future 是 Send, 但是在它内部短暂的使用 NotSend 依然是安全的,原因在于它的作用域并没有影响到 .await,下面来试试声明一个变量,然后让 .await 的调用处于变量的作用域中试试:

async fn foo() {
    let x = NotSend::default();
    bar().await;
}

不出所料,错误如期而至:

error: future cannot be sent between threads safely
  --> src/main.rs:17:18
   |
17 |     require_send(foo());
   |                  ^^^^^ future returned by `foo` is not `Send`
   |
   = help: within `impl futures::Future<Output = ()>`, the trait `std::marker::Send` is not implemented for `Rc<()>`
note: future is not `Send` as this value is used across an await
  --> src/main.rs:11:5
   |
10 |     let x = NotSend::default();
   |         - has type `NotSend` which is not `Send`
11 |     bar().await;
   |     ^^^^^^^^^^^ await occurs here, with `x` maybe used later
12 | }
   | - `x` is later dropped here

提示很清晰,.await在运行时处于 x 的作用域内。在之前章节有提到过, .await 有可能被执行器调度到另一个线程上运行,而 Rc 并没有实现 Send,因此编译器无情拒绝了咱们。

其中一个可能的解决方法是在 .await 之前就使用 std::mem::drop 释放掉 Rc,但是很可惜,截止今天,该方法依然不能解决这种问题。

不知道有多少同学还记得块作用域 { … } 在 Rust 中其实具有非常重要的作用(特别是相比其它大多数语言来说时):可以将变量声明在语句块内,当语句块结束时,变量会自动被 drop,这个规则可以帮助我们解决很多借用冲突问题,特别是在 NLL 出来之前。

async fn foo() {
    {
        let x = NotSend::default();
    }
    bar().await;
}

是不是很简单?最终我们还是通过 Drop 的方式解决了这个问题,当然,还是期待未来 std::mem::drop 也能派上用场。

递归使用 async fn

在内部实现中,async fn 被编译成一个状态机,这会导致递归使用 async fn 变得较为复杂, 因为编译后的状态机还需要包含自身。

// foo函数:
async fn foo() {
    step_one().await;
    step_two().await;
}
// 会被编译成类似下面的类型:
enum Foo {
    First(StepOne),
    Second(StepTwo),
}

// 因此recursive函数
async fn recursive() {
    recursive().await;
    recursive().await;
}

// 会生成类似以下的类型
enum Recursive {
    First(Recursive),
    Second(Recursive),
}

这是典型的动态大小类型,它的大小会无限增长,因此编译器会直接报错:

error[E0733]: recursion in an `async fn` requires boxing
 --> src/lib.rs:1:22
  |
1 | async fn recursive() {
  |                      ^ an `async fn` cannot invoke itself directly
  |
  = note: a recursive `async fn` must be rewritten to return a boxed future.

如果认真学过之前的章节,大家应该知道只要将其使用 Box 放到堆上而不是栈上,就可以解决,在这里还是要称赞下 Rust 的编译器,给出的提示总是这么精确:recursion in an `async fn` requires boxing。

就算是使用 Box,这里也大有讲究。如果我们试图使用 Box::pin 这种方式去包裹是不行的,因为编译器自身的限制限制了我们。为了解决这种问题,我们只能将 recursive 转变成一个正常的函数,该函数返回一个使用 Box 包裹的 async 语句块:

use futures::future::{BoxFuture, FutureExt};

fn recursive() -> BoxFuture<'static, ()> {
    async move {
        recursive().await;
        recursive().await;
    }.boxed()
}

在 trait 中使用 async

在目前版本中,我们还无法在 trait 中定义 async fn 函数,不过大家也不用担心,目前已经有计划在未来移除这个限制了。

trait Test {
    async fn test();
}

运行后报错:

error[E0706]: functions in traits cannot be declared `async`
 --> src/main.rs:5:5
  |
5 |     async fn test();
  |     -----^^^^^^^^^^^
  |     |
  |     `async` because of this
  |
  = note: `async` trait functions are not currently supported
  = note: consider using the `async-trait` crate: https://crates.io/crates/async-trait

好在编译器给出了提示,让我们使用 async-trait 解决这个问题:

use async_trait::async_trait;

#[async_trait]
trait Advertisement {
    async fn run(&self);
}

struct Modal;

#[async_trait]
impl Advertisement for Modal {
    async fn run(&self) {
        self.render_fullscreen().await;
        for _ in 0..4u16 {
            remind_user_to_join_mailing_list().await;
        }
        self.hide_for_now().await;
    }
}

struct AutoplayingVideo {
    media_url: String,
}

#[async_trait]
impl Advertisement for AutoplayingVideo {
    async fn run(&self) {
        let stream = connect(&self.media_url).await;
        stream.play().await;

        // 用视频说服用户加入我们的邮件列表
        Modal.run().await;
    }
}

不过使用该包并不是免费的,每一次 trait 中的 async 函数被调用时,都会产生一次堆内存分配。对于大多数场景,这个性能开销都可以接受,但是当函数一秒调用几十万、几百万次时,就得小心这块儿代码的性能了!

参考

  1. https://github.com/rustcn-org/async-book
  2. https://www.bilibili.com/video/BV1Ki4y1C7gj
Rust 基于 await、async 的异步编程和纤程、协程的实现 它使得在 Rust 中编写异步代码变得更加简单和直观,通过结合 async/await 语法,你可以编写出清晰易读的异步代码,提高程序的并发性能和响应能力。这意味着虽然异步操作本身不会启动新线程,但它们可以在现有的线程池中的线程上执行,从而实现高效的并发处理。async 关键字用于声明一个函数是异步的,这意味着该函数可以包含挂起执行的操作(如网络请求或文件读写),而不会阻塞整个程序的执行。异步操作的执行是由运行时库(如Tokio)管理的,它们可能会在线程池中的线程上调度这些操作,以实现高效的并发处理。 阅读详情

相关推荐

rust教程 第一章 —— 初识rust

本文主要的目的就是认识一下rust这门语言的基本概念

余识- 5万+

Rust 全面指南:从基础到高级,一网打尽 Rust 的编程知识

Rust 是一门由 Mozilla 开发的系统编程语言,它具有高性能、并发性、内存安全等特点,被广泛用于开发操作系统、网络服务器、游戏引擎等高性能应用程序。Rust 的设计目标是提供强大的抽象能力和内存安全保障,同时保持高效的执行效率。它与 C++ 相比,具有更为严格的类型系统和所有权系统,可以避免很多内存安全问题。Rust 也是一门多范式编程语言,支持函数式编程、面向对象编程和泛型编程等多种编程范式。本教程将带你从入门到精通 Rust,掌握 Rust 的基础语法、类型系统、所有权系统、并发编程、泛型编程等

qq_36678837的博客 1万+

Rust编程基础教程:物联网应用开发

Rust是一种多样化的编程语言,它具有极高的性能、安全性和可靠性,适合用在关键任务系统软件中。Rust编译器能够进行高效的代码优化,包括栈上分配、类型推断、借用检查、循环优化等等。Rust的并发机制、内存管理、面向对象编程特性使得它成为一个适用于复杂系统的优秀语言。Rust可以用于开发服务器应用程序、嵌入式设备驱动程序、实时系统软件、浏览器插件和游戏引擎。近年来,Rust的热度已经得到了很大的提升。目前,Rust已逐渐成为主流的语言,尤其是在云计算、微服务、区块链等领域。

AI天才研究院 1459

一文读懂Rust的async

因为这些运行时之间有很多`相通`的地方,熟悉了一个再去熟悉其它的就简单了。就像我们学习编程语言一样,学好学深一门编程语言,再去学习其它的语言就快了。不要一开始就几门语言一起学,这样很可能实际开发时这也不行那也不行换来换去还是不能开发出东西.

一个不安分的程序员 9643

【Rust异步编程】Rust异步编程原理解析、标准库实现与第三方库实现详解

异步编程是一种允许程序在等待某些操作(如I/O)完成时继续执行其他任务的编程范式。与传统的同步编程相比,异步编程可以显著提高程序的吞吐量和资源利用率。 在Rust中,异步编程具有以下特点: 非阻塞执行:任务可以在等待时让出控制权 轻量级:异步任务(称为future)比线程更轻量 显式标记:使用async/await语法明确标识异步代码

景天科技苑 4万+

【低空经济】无人机充电站设计方案

随着无人机技术的快速发展和广泛应用,其在物流、农业、巡检、救援等领域的应用日益普及。然而,无人机的续航能力一直是限制其大规模应用的关键因素之一。为了解决这一问题,无人机充电站的设计与建设显得尤为重要。本项目旨在设计一套高效、智能、可扩展的无人机充电站系统,以满足不同场景下无人机的充电需求,提升其作业效率和应用范围。无人机充电站的设计将围绕以下几个核心目标展开:首先,充电站应具备快速充电能力,以减少无人机的停机时间,提高作业效率;

2960

【Rust异步】async和await异步编程实战:高并发任务处理全解析

async 和 await 是 Rust 中用于异步编程的关键字。 async 用于定义异步函数,表示函数体中包含异步代码。 await 用于等待异步操作完成,并返回异步操作的结果。 • 异步函数使用 async 关键字定义,并返回实现了 Future Trait 的类型。 异步函数可以在其他异步函数中使用 await 关键字等待异步操作完成。 调用异步函数时,会返回一个实现了 Future Trait 的对象,可以通过调用.await 方法等待结果。

景天科技苑 2万+

开启虚拟人物互动新时代:探索相芯Avatar SDK驱动功能(Android)

相芯SDK提供了男孩和女孩Avatar驱动模型,这些模型具备半身驱动和全身驱动的功能,使得虚拟角色能够根据用户的面部表情和动作实时模仿和响应。半身驱动功能: 相芯SDK的男孩和女孩Avatar驱动模型具备上身驱动功能,即能够通过用户的面部表情和动作来驱动虚拟角色的上半身行为。这意味着用户的面部表情、头部转动和上半身姿态等动作可以直接映射到虚拟角色上,使得虚拟角色能够栩栩如生地模仿用户的动作。例如,用户微笑时,虚拟角色会展现出笑容;用户转动头部时,虚拟角色也会相应地转动头部。

Faceunity2023的博客 633

Rust Async 异步编程(一):入门

Rust Async 异步编程(一):入门

ProgramNovice的博客 1388

极空间NAS零基础部署Wiki.js:Docker+PostgreSQL全流程避坑指南

本文提供了一份在极空间NAS上从零部署Wiki.js私有知识库的详细指南。通过Docker容器化技术,结合PostgreSQL数据库,手把手讲解环境准备、核心组件配置、性能优化及安全加固等全流程,并特别针对极空间平台特性提供了避坑要点,帮助用户打造安全、高效、自主可控的知识管理系统。

3a9bq4r8t2y的博客 488

C# typeof() 和 GetType()区别

总得来说他们都是为了获取某个实例具体引用的数据类型System.Type。 1、GetType()方法继承自Object,所以C#中任何对象都具有GetType()方法,x.GetType(),其中x为变量名,由x实例返回得到一个类型 2、typeof(x)中的x,必须是具体的类名、类型名称等,不可以是变量名称,返回一个类型 3、System.Type.GetType(),有两个重载方法 比如有这样一个变量i: Int32 i = new Int32(); 使用GetType(),i...

Peter的博客 1956

Rust异步编程 Async Await 入门

要了解什么是异步代码,我们首先来谈谈同步代码。println!println!上述语句按照明确的顺序执行,从上到下一个接一个地执行。” 打印,然后读取并打印Cargo.toml的内容。这种模式在正常操作下非常好 - 但有时代码需要在当前上下文在等待其他内容时暂停 - 这通常称为阻塞。换句话说,当一段代码被阻塞时,它实际上处于暂停状态,等待特定操作完成才能继续。例如,当等待文件系统、网络通信、数据库事务甚至一段时间过去时,就会发生这种情况。在此阻塞状态期间,程序保持空闲状态,无法同时执行其他任务。

xuejianxinokok的专栏 1657

EtherCAT从站EEPROM组成信息详解(2):字8-15产品标识区

以实际的从站EEPROM数据为例,参照EtherCAT的SII规范,详细分析了从站EEPROM字8-字15的产品标识区内容组成。

kevin1499的博客 719

Rust 异步编程详解(async/await)

Rust异步编程通过async/await语法和Future trait实现高效I/O处理,核心机制包括状态机模型和运行时调度。文章详细解析了异步编程基础、核心组件(Future trait、async/await语法)、工作流程,并提供了HTTP请求等代码示例。同时探讨了错误处理、任务取消、Pin/Unpin等高级话题,比较了不同运行时的适用场景。最佳实践建议选择合适运行时、避免阻塞操作,并指出异步编程在性能和安全方面的优势,以及调试复杂度等限制。附带的并发网络请求示例展示了tokio运行时的实际应用,包

本博客分享软硬件一体化开发实战。硬件涵盖 PCB 设计、STM32 等 MCU 底层开发;软件包含 C++、Rust,Qt/QML、PyQt、Slint、TouchGFX、LVGL 各类 GUI 框架。方便软硬件开发者查阅学习。欢迎关注! 2224

大模型——Dify:知识库与外部知识库

摘要 Dify的知识库功能为开发者提供了动态知识管理解决方案,超越了大模型静态预训练数据的限制。知识库支持多种文档格式(如txt、pdf、docx等),通过文档分段、索引设置和检索优化机制提升LLM的响应准确性。系统提供高质量和经济两种索引模式,前者采用向量化检索技术,后者使用关键词匹配以降低成本。知识库支持元数据管理,包括自动生成的内置元数据和用户自定义元数据,便于内容分类和检索。对于高级需求,Dify还支持连接外部知识库,通过API实现跨平台数据共享和检索优化,满足企业级知识管理需求。

732

HFSS实战项目——GPS北斗双模微带天线

本文设计了一款方形圆极化微带天线应用于北斗B1和GPSL1频段,满足高增益、低VSWR、适当方向图等要求。设计中心频率为1568MHz,介质板采用厚度h=1.6mm的Arlon AD450(tm)板,相对介电常数为4.5,采用探针馈电。

2401_87464239的博客 1769

Rust 异步编程 async/await

Rust 的异步编程模型 async/await 提供了一种简洁、高效的方式来处理异步操作。它允许开发者以一种更自然和直观的方式来处理异步操作,同时保持了 Rust 的安全性和性能。通过 async/await,Rust 为异步编程提供了一流的语言支持,使得编写高效且可读性强的异步程序变得更加容易。

QQ672168的博客 1344

MATLAB基础应用精讲-【强化学习】Actor - Critic 算法(附python代码实现)

这种架构结合了策略梯度方法(Policy - Gradient Methods)和价值函数估计方法(Value Function Estimation Methods)的优点。在强化学习中,智能体(Agent)需要通过与环境(Environment)的交互来学习最优的行为策略,以获得最大的累积奖励(Cumulative Reward)。

qq_36130719的博客 364

异步编程:Rust 的 async/await 详解

Rust异步编程模型通过async/await语法、Future Trait和异步运行时(Tokio/async-std)实现高效非阻塞I/O操作。async标记异步函数返回Future,await暂停但不阻塞线程,允许运行时调度其他任务。异步运行时管理任务调度和事件循环,Tokio提供完整的异步I/O支持。通过tokio::spawn和select!宏可实现并发任务处理。这种模型结合Rust的内存安全特性,使开发者能编写高性能、可维护的异步应用,特别适合I/O密集型场景。

2501_93870138的博客 993

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Rust异步编程之async与.await

介绍了Rust中异步编程基本语法async与.await和Future,以及常见的运行时

飞鹰技术 1098

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

文章标题:深入探索 Rust 中的异步编程:使用 async 和 await 提高开发效率

在现代软件开发中,异步编程成为了处理并发任务和提高性能的重要手段。尤其是在高并发、高 I/O 的应用中,如何高效地进行并发控制是开发者面临的一大挑战。而在众多编程语言中,Rust 作为近年来广受关注的系统编程语言,其在内存安全和性能上的优势被许多开发者称赞。而异步编程在 Rust 中也得到了很好的支持,借助async和await关键字,我们可以方便地实现高效的异步编程。本文将深入介绍 Rust 中的异步编程机制,包括如何在 Rust 中使用async和await。

桂月二二博客 1260

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Rust异步编程——1、Async 编程简介

async是 Rust 选择的 异步编程模型,下面我们来介绍下它的优缺点,以及何时适合使用。

码刀攻城 666

Rust Async 异步编程(十一):Async 生态

Rust Async 异步编程(十一):Async 生态

ProgramNovice的博客 1009

Rust Async 异步编程(二):Future trait

Rust Async 异步编程(二):Future trait

ProgramNovice的博客 716

异步编程革命:Rust async/await完全指南

异步编程革命:Rust async/await完全指南 本文深入探讨了Rust异步编程的核心机制,从Future执行模型与任务调度机制、Pin和Unpin的内存安全原理,到Stream流处理与多Future并发技术,最后通过异步Web服务器实战开发展示完整应用。文章详细解析了Rust异步编程的底层实现原理和高级应用技巧,为开发者提供从基础到实战的完整指南。 Future执行模型与任务调度机制 在R...

gitblog_00027的博客 1262

Rust 异步编程实战

在复杂的应用中,定义自定义错误类型可以提高代码的可读性和可维护性。write!(f, "{:?}", self)async-std提供了一个简化的异步编程接口,与 Rust 标准库接口相似,使得异步编程更易于理解。println!("获取的数据: {}", data);});Tokio是一个功能强大的异步运行时,适用于构建 I/O 密集型应用。它支持异步 I/O、任务调度和定时器等功能。// 模拟异步延迟println!("延迟完成");

好看资源网的博客 1182
上一篇: Rust Async 异步编程(九):select!
下一篇: Rust 项目实战:异步多线程 Web 服务器
UestcXiye
博客等级 码龄8年 9031粉丝 1306原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值