原文:Rust futures: thread::sleep and blocking calls inside async fn
URL: https://blog.hwc.io/posts/rust-futures-threadsleep-and-blocking-calls-inside-async-fn/

近来,关于Rust的futuresasync/await如何工作(“blockers”,哈哈),我看到存在一些普遍的误解。很多新用户为async/await带来的重大改进而感到兴奋,但是却被一些基本问题所困扰。即使有了async/await,并发依然很难。文档还在进一步充实,阻塞/非阻塞之间的交互很棘手。希望本文对你有所帮助。

(本篇主要是关于特定的痛点;有关Rust中的异步编程的概述,请转至本书)

TLDR(Too Long Didn't Read):小心在async fn中使用昂贵的阻塞调用!如果不确定, 鉴于Rust std库中几乎所有都是阻塞的,所以就要注意哪些调用是耗时的!

虽然我认为任何人都可能犯这个错误(在引入足够的负载来显著地阻塞线程之前,往往察觉不到),但是初学者尤为如此。下面的场景可能有点冗长,但我认为有必要展示一下在async fn中实现阻塞调用是多么容易。

不要用 std::thread::sleep sleep

在研究了一个简单的示例之后,Rust异步新手可能要做的第一件事就是去验证程序真正实现了异步。因此,我们使用Rust异步书籍中的示例:

use futures::join;
​
async fn get_book_and_music() -> (Book, Music) {let book_fut = get_book();let music_fut = get_music();join!(book_fut, music_fut)
}

即使你在get_book和get_music内部打日志,也无法通过简单的方式来判断它们是同时运行的,因为任何一次运行都可能产生恰好与代码顺序匹配的输出。你必须多次运行该程序,才能查看日志记录顺序是否可以翻转(如果不翻转怎么办?)。

如果想看到get_book和get_music是100%同时运行,你可能会想到记录它们的开始时间,并查看开始时间是否相同。但是,等等,如果开始时间仍然是串行的,但fn运行得如此之快,看起来仍然像是并发该怎么办?

引入一个延迟!比如(清楚起见,使用伪码):

async fn get_book() {println!("book start: time {}", current_time());std::thread::sleep(one_second);println!("book end: time {}", current_time());
}

在get_book和get_music内部延迟1秒,我们希望,如果是并发的话,则会看到以下的输出:

book start: time 0.00
music start: time 0.00
book end: time 1.00
music end: time 1.00

如果是串行,我们预期是:

book start: time 0.00
book end: time 1.00
music start: time 1.00
music end: time 2.00

你认为会发生什么, 串行或并发?

你已经读了这篇文章的标题,可能会猜到get_book和get_music是按顺序执行的。但为什么!?异步fn中的所有内容不是都应该同时运行吗?

在继续解释之前,可以看个问题已经多次被问到:

reddit 1 reddit 2 reddit 3 stackoverflow 1

因此,如果你也犯了这个错误,不用担心,其他许多人也有同样的经历。

什么搞错了?为什么async不行?

我不会在这里深入讨论futuresasync/await(本书是一个很好的起点)。我只想指出造成困惑的两个可能的根源:

std::thread::sleep 会阻塞?

对于新手来说,std::thread::sleep会造成阻塞可能并不是显而易见的。尽管事后看起来很明显,但是当尝试掌握全新的程序执行范式时,却很容易忽略。

即使你大致了解并发,也可能不知道thread::sleep是具体如何实现的。一些上层推理加上一些示例(例如上述)可能会帮助你理解。但是文档中并没有明说“此调用是阻塞的,你不应该在异步上下文中使用它”,并且非系统程序员可能不会过多地考虑“将当前线程置于睡眠状态”。

(具有讽刺意味的是,如果人们的异步编程的心智模型是让Future进入“睡眠”状态从而得以让其他工作发生,那么thread::sleep可能会特别令人困惑)。

async 可以做什么?

但是有些人可能会说:“如果thread::sleep阻塞了怎么办?不是把它放在async fn中就好了吗?”

为了理解那些在线讨论,(就要知道)他们的想法是以为async可以使代码块或函数内部的所有内容异步。

首先,我想说这是有意义的;async/await存在的部分原因是它使每个人都容易进行异步操作。而且,如果你从较高的层次上理解了并发模型(事件循环,通常是尝试不阻塞线程),那么可能没有特定的理由导致async不能仅仅通过使事物定义为异步来起作用。那绝对是最简单,最符合人体工程学的方式。

不幸的是,这不是Rust的async范式的工作方式。async功能很强大,但从本质上讲,它只是提供了一种更好的处理Futures的方法。而且Future不只是自动将阻塞调用移到一边以允许完成其他工作;它要结合使用具备轮询和异步运行时这种完全独立的系统,才能进行异步舞蹈。在该系统内进行的任何阻塞调用仍将处于阻塞状态。

这可能会造成一些困惑,因为async/await允许我们编写看起来更像常规(阻塞)代码的代码。那就是async/awaitawait部分进入的地方。当你在async块中awaitfuture时,它能够将自己安排在线程外并为其他任务让路。阻塞代码可能看起来很相似,但是由于它不是future,所以无法await,也无法为其他任务腾出空间。

因此,下面不会阻塞,但是await可以让你编写看起来与阻塞调用非常相似的代码:

async {let f = get_file_async().await;let resp = fetch_api_async().await;
}

下面在每行调用时阻塞:

async {let f = get_file_blocking();let resp = fetch_api_blocking();
}

下面将不能通过编译:

async {let f = get_file_blocking().await;let resp = fetch_api_blocking().await;
}

在这里什么也没有发生(您必须在async内部awaitfutures!):

async {let f = get_file_async();let resp = fetch_api_async();
}

总的来说,最好将async视为允许在函数或块中 await 的东西,但实际上并不会使任何东西异步。

如何不阻塞

如果想要异步fn取消阻塞该怎么办?

你可以找到一个异步替代方案:当thread::sleep阻塞时,你可以使用它们(取决于你选择的运行时生态系统):

  • async_std::task::sleep (1.0)
  • tokio::time::delay_for (0.2.0)

tokioasync_std都为其他阻塞操作(例如文件系统和tcp流访问)提供了异步替代方法。

另一个选择是将阻塞调用移到另一个线程。

  • tokio::task::spawn_blocking (0.2.0)
  • async_std::task::spawn_blocking (1.0)

这要求你的运行时具有专用于卸载阻塞调用的机制(例如线程池)。

我还提出了一些问题,试图防止其他人陷入这个陷阱:

  • async-book
  • clippy

结语

希望该博客能够阐明有关阻塞调用如何与Rust的并发模型进行交互的一些信息!随时提供反馈给我。

ttf_openfont可以多次调用吗_【译文】Rust futures: async fn中的thread::sleep和阻塞调用...相关推荐

  1. Rust futures: async fn 中的 thread::sleep 和阻塞调用

    原文:Rust futures: thread::sleep and blocking calls inside async fn URL: https://blog.hwc.io/posts/rus ...

  2. ttf_openfont可以多次调用吗_西门子OB块、FC块、FB块、DB块之间有什么关系?如何调用?...

    我们在用西门子STEP或者TIA等编程程序的时候,必不可少的就是块编程以及调用,那么什么是块?他们之间有什么关系?如何调用?用在什么场合? 首先,建立一个新的文件,创建一个工艺所需的项目,对项目进行硬 ...

  3. angular 设置接口调用地址_[译] 关于 Angular 的变化检测,你需要知道的一切

    原文地址:Everything you need to know about change detection in Angular 原文作者:Max, Wizard of the Web 译文出自: ...

  4. python用类名直接调用方法_一文读全 Python 的面向对象编程方法

    背景介绍: Python 支持三种形式的编程,分别是:"命令式"."函数式"和"面向对象式". 很多优秀的开源 Python 项目里都用到了 ...

  5. vue created 调用方法_深入解析 Vue 的热更新原理,偷学尤大的秘籍?

    大家都用过 Vue-CLI 创建 vue 应用,在开发的时候我们修改了 vue 文件,保存了文件,浏览器上就自动更新出我们写的组件内容,非常的顺滑流畅,大大提高了开发效率.想知道这背后是怎么实现的吗, ...

  6. window.addeventlistener 不能调用方法_方法入门(基础篇五)

    前边写运算符的时候,都是创建一个类和一个main方法,这样会有很多重复代码,为了减少重复代码,我们可以使用方法来实现. 什么是方法? Java的方法是语句的集合,它们在一起执行一个功能. 方法是解决一 ...

  7. mybatis 调用存储过程_你真的该进来了解下MyBatis的SQL工作流程了

    前言 MyBatis可能很多人都一直在用,但是MyBatis的SQL执行流程可能并不是所有人都清楚了,那么既然进来了,通读本文你将收获如下: 1.Mapper接口和映射文件是如何进行绑定的 2.MyB ...

  8. 线程中这么调用类_这些线程知识总结是真的到位!java开发两年的我看的目瞪口呆

    前言 什么是线程:程序中负责执行的那个东东就叫做线程(执行路线,进程内部的执行序列),或着说是进程的子任务. Java中实现多线程有几种方法 继承Thread类: 实现Runnable接口: 实现Ca ...

  9. java 继承调用_【JAVA学习】继承中函数调用机制

    类(类的对象)在调用函数时,究竟哪一个函数被调用,是存在特定的机制的,现在我们来梳理下其步骤. 步骤一:当其调用一个函数的时候,编译器根据其声明的变量类型和调用的函数名字找到所有此"类&qu ...

最新文章

  1. delphi socket 流的使用_基于TCP协议的Socket编程和通信_单向通信
  2. ComponentName(String pkg, String cls)
  3. 【swjtu】数据结构实验8_平衡二叉排序树
  4. 3.MATLAB界面介绍
  5. windowsXPsp3怎样恢复桌面IE图标(非快捷方式)
  6. 【小白成长撸】--二分查找
  7. ABAP和Java的destination和JNDI
  8. braintree php 开发,php – 如何为使用Braintree和静态方法调用的端点编写集成测试...
  9. 算法 --- 归并排序的js实现
  10. React-生命周期杂记 1
  11. 算法设计与分析——回溯法——装载问题
  12. 测试环境安装配置obproxy(社区版)
  13. SignalR 跨域解决方案全面
  14. 程序员想创业首先要突破自己三关(转)
  15. linux异常,samba共享目录看不到文件,本机访问不了自己的mysql,nginx访问提示找不到...
  16. 高德地图GPS经纬度转高德经纬度
  17. 分形艺术能让你感受上帝的力量——新锐100设计师风达专访
  18. 转载---英语 是否 双写 规则
  19. Epub电子书格式(四)
  20. 使用python将图片改为灰度图或黑白图

热门文章

  1. 关于 Angular Component ngOnDestroy 钩子函数的调用时机
  2. 在 SAP Analytics Cloud 上开发和使用 Queries
  3. SAP CRM WebClient UI 培训教程
  4. ABAP Development Tool 代码模板和其他一些实用技巧汇总
  5. SAP Spartacus 升级时关于 schematics 的更新
  6. SAP Spartacus ProductService.get的几个调用场景
  7. package javax.servlet.jsp.tagext does not exist的错误消息如何解决
  8. odata.publish = true的CDS view激活之后,后台发生了什么事情
  9. Could not open app - SAP UI5 error message
  10. Error Message - 400 File is not contained in a resource root