七爪源码:Rust 异步第 2 部分
基本异步用法
这是了解Rust异步编程基础的3部分系列中的第2部分。第1部分侧重于围绕异步编程的概念,特别是Rust如何实现它。在这一节中,我们将看一些示例代码。
首先,让我们使用Cargo和cargonewrustasync–bin创建一个新项目
现在,打开生成的Cargo.toml文件并将futures-rscrate添加到依赖项部分。您可以通过上面的链接了解更多信息并找到完整的文档,但总而言之,它是异步运行时的基本实现。您的toml文件应类似于以下文件:
futures-rscrate实现了Rust的一些异步编程核心抽象。稍后我们将更深入地讨论这些,但就目前而言,Future特征与我们最相关。
假设我们要在家做早餐。我们可能会选择煮一些咖啡、鸡蛋,因为我想保持健康,所以用烤箱做一些培根。我的过程类似于启动咖啡机并冲泡咖啡。在这种情况下,我可以预热烤箱。当烤箱预热时,我可以把培根拿出来准备烤箱。培根准备好后,我可以将其放入烤箱,给自己倒一些咖啡,然后在等待培根完成的同时煮鸡蛋。我相信你现在明白了。如果我们要同步准备早餐,一个接一个的任务,完成准备早餐需要的时间要长得多,而且到我们吃的时候大部分都已经冷了。
让我们编写一个简单的应用程序来尝试使用异步代码来模仿这种方法。打开src目录下的main.rs文件并添加以下代码。
首先,我们从期货箱中导入一个非常简单的执行器。在Rust中,future是懒惰的。稍后我们将更详细地讨论这一点,但创建未来本身并没有任何作用。它需要由执行者驱动完成。这就是on_block()执行器的工作。它接受Future作为参数并在阻塞主线程时执行该代码。
上面的代码不是惯用的,因为我们创建了代表异步函数的Future,立即调用block_on()执行器,等待异步代码完成,然后对其余的异步函数重复这个过程。这有效地使执行同步。让我们稍微调整一下代码,让它看起来更惯用一些。
所以我们知道我们可以启动咖啡机并让它在我们在烤箱中烹饪培根时让它工作,所以让我们在开始鸡蛋之前开始这两个任务。
首先,我们将添加一个名为make_coffee_and_bacon()的新异步函数,如下所示:
接下来,我们将添加另一个名为async_main()的新异步函数:
最后,我们将现有的main()函数更改为如下所示:
让我们一次一个地了解这些变化。首先,我们为咖啡和培根任务创建一个新的异步函数。你会注意到我们在函数内部使用了await。在async函数中使用await类似于使用on_block()来等待Future解决,但不是阻塞线程,而是在future被阻塞时允许其他任务运行,例如,等待来自套接字或流的输入.
其次,我们添加了async_main()函数。这是一个非常简单的改变。由于await只能在async函数或async代码块中使用,而main()函数不能使用async关键字进行注解。虽然运行main()的线程将阻塞,直到block_on()调用解决,从async_main()到执行链的所有内容都将异步运行。第三行是新的。futures::join!()宏类似于await关键字,但它对多个Future进行操作,仅在所有Future完成时才返回,而不管完成的顺序如何。
之前我提到期货是“懒惰的”,因为它们自己什么都不做。除非被驱动完成,否则它们永远不会返回值。这可能看起来很奇怪,特别是如果您熟悉其他语言如何实现异步代码,但这是语言设计者深思熟虑的选择。通过让Future变得懒惰,创建异步函数成为零成本操作。“零成本”是指除非您实际使用它们,否则创建Future不会对运行时产生影响。如果您想查看这种惰性的可观察示例,只需从make_coffee_and_bacon()函数中对make_coffee()和make_bacon()的调用中删除.await,然后运行代码。您会注意到,当您在删除await后运行它时,它永远不会打印任何输出。
我想指出,在这个特定示例中,我们的异步函数的执行顺序不会改变。您可以在三个函数中的任何一个或所有函数中随机休眠以更改执行时间,但这不会影响打印语句出现的顺序。
这可能看起来违反直觉,毕竟这应该是异步执行。让我们来看看为什么。如果您还记得本系列的第一篇文章,我们提到await关键字被生成的状态机用作指定原子执行单元的标记。例如,在异步函数make_coffee_and_bacon()中,我们首先调用make_coffee().await,然后调用make_bacon().await。在生成的状态机中,从函数开始到第一个await关键字的所有内容都被视为一个原子单元。同样,在调用make_coffee().await之后直到下一个await关键字的所有内容都是另一个原子单元。这意味着函数make_coffee()将在运行make_bacon()之前完成。await关键字本身并不会真正推动Future完成。驱动异步代码完成的是block_on()调用。我之前使用了执行者这个词,但并没有真正定义它。执行器负责驱动它负责完成的所有异步代码。在我们的例子中,我们将async_main()传递给block_on()执行器,该执行器负责驱动async_main()包含的所有异步代码完成,但block_on()没有实现任何允许它执行的逻辑多路执行。为了允许使用async/await的代码以异步方式执行,我们需要使用更复杂的执行器。我是这样想的;未来是我们想要执行的计算,未来执行的任务调度,执行者确保所有任务都完成,并协调相互依赖关系。
顺便说一句,join!()宏存在的原因是允许更灵活的任务组合。例如,如果我们将block_on()换成更复杂的执行器,我们不需要对我们的异步代码进行任何其他更改。
这是3部分系列中的第2部分。我试图以一种简单的方式呈现这些信息,同时仍然提供足够的细节来提供基本的理解。结果比我预想的要困难一些。在下一节中,我们将看看Rust异步的更现实的实现,并讨论一些可用的不同运行时,以及一些陷阱。如果您做到了这一点,请告诉我,以便我知道您是否觉得这很有用,并关注我以了解该系列的下一篇文章何时发布!
关注七爪网,获取更多APP/小程序/网站源码资源!
主题测试文章,只做测试使用。发布者:最新稳定辅助网,转转请注明出处:https://www.744broad.com/13468.html