Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the internal running mechanism of rust asynchronous code async/.await?

2025-01-21 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/01 Report--

This article focuses on "what is the internal running mechanism of rust asynchronous code async/.await". Interested friends may wish to take a look. The method introduced in this paper is simple, fast and practical. Let's let the editor take you to learn "what is the internal running mechanism of rust asynchronous code async/.await?"

0. Prepare the Rust practice environment

First, let's create a Cargo project:

~ $cargo new-- bin sleepus-interruptus

If you want to be consistent with the compiler used in the tutorial, you can add a rust-toolchain file with content 1.39.0.

Before proceeding, run cargo run to make sure the environment is fine.

1. An alternating display Rust program

We are going to write a simple program that can display 10 Sleepus messages with an interval of 0.5 seconds and 5 Interruptus messages with an interval of 1 second each time. Here is a fairly simple rust implementation code:

Use std::thread:: {sleep}; use std::time::Duration;fn sleepus () {for i in 1.. println 10 {println! ("Sleepus {}", I); sleep (Duration::from_millis (1000));} fn interruptus () {for i in 1.. for i in 5 {println! ("Interruptus {}", I); sleep (Duration::from_millis (1000)) }} fn main () {sleepus (); interruptus ();}

However, the above code performs two operations synchronously, displaying all the Sleepus messages before displaying the Interruptus messages. What we expect is that these two messages are interlaced, that is, Interruptus messages can interrupt the display of Sleepus messages.

There are two ways to achieve the goal of interlaced display. The obvious one is to create a separate thread for each function and then wait for the thread to finish executing.

Use std::thread:: {sleep, spawn}; fn main () {let sleepus = spawn (sleepus); let interruptus = spawn (interruptus); sleepus.join (). Unwrap (); interruptus.join (). Unwrap ();}

It should be pointed out that:

We use spawn (sleepus) instead of spawn (sleepus ()) to create threads. The latter will immediately execute sleepus () and pass its execution result to spawn, which is not what we expected-I use join () in the main function to wait for the child thread to finish, and use unwrap () to handle failures that can occur because I am lazy.

Another way to do this is to create a worker thread and then call one of the functions in the main thread:

Fn main () {let sleepus = spawn (sleepus); interruptus (); sleepus.join (). Unwrap ();}

This method is more efficient because only one additional thread needs to be created and there are no side effects, so I recommend this method.

However, neither of these methods is an asynchronous solution! We use two threads managed by the operating system to perform two synchronous tasks concurrently! Next, let's try how to make two tasks execute together in a single thread!

2. Realize the alternating display program with Rust asynchronous async/.await.

We'll start with a higher level of abstraction and then delve into the details of rust asynchronous programming. Now let's rewrite the previous application in async style.

First add the following dependencies to Cargo.toml:

Async-std = {version = "1.2.0", features = ["attributes"]}

Now we can rewrite the application as follows:

Use async_std::task:: {sleep, spawn}; use std::time::Duration;async fn sleepus () {for i in 1.. println 10 {println! ("Sleepus {}", I); sleep (Duration::from_millis (500)) .await;}} async fn interruptus () {for i in 1.. Duration::from_millis 5 {println! ("Interruptus {}", I); sleep (Duration::from_millis (1000)). Await } # [async_std::main] async fn main () {let sleepus = spawn (sleepus ()); interruptus () .await; sleepus.await;}

The main changes are as follows:

Instead of using the sleep and spawn functions in std::thread, we use async_std::task. -add async before both sleepus and interruptus functions

After invoking sleep, we added .await. Note that it is not a .await () call, but a new syntax.

Use the # [async_std::main] attribute on the main function

There is also an async keyword before the main function.

We now use spawn (sleepus ()) instead of spawn (sleepus), which means calling sleepus directly and passing the result to spawn

Add .await to the call to interruptus ()

Instead of using join () for sleepus, use .await syntax instead

There seems to be a lot of changes, but in fact, our code structure is basically the same as the previous version. Now the program runs as we expected: a single thread is used for non-blocking calls.

Next, let's analyze what the above changes mean.

3. The function of async keyword

Adding async before the function definition mainly does the following three things:

This will allow you to use the .await syntax inside the function. We'll talk about this in depth next.

It modifies the return type of the function. Async fn foo ()-> Bar actually returns impl std::future::Future

It automatically encapsulates the resulting value into a new Future object. We will show this in more detail below.

Now let's move on to point 2. In the standard library of Rust, there is a trait,Future named Future that has an association type Output. This trait means: I promise to give you a value of type Output when I finish the task. For example, you can imagine that an asynchronous HTTP client might implement this:

Impl HttpRequest {fn perform (self)-> impl Future {...}}

We need some non-blocking HTTP O when sending the request. We don't want to block the calling thread, but we need to get the response result eventually.

The result type of async fn sleepus () is implied as (). So the Output of our Future should also be (). This means that we need to modify the function to:

Fn sleepus ()-> impl std::future::Future

However, if you only modify here, the following error will occur in the compilation:

Error [E0728]: `await` is only allowed inside `async` functions and blocks-> src/main.rs:7:9 | 4 | fn sleepus ()-> impl std::future::Future {|-this is not `async`.7 | sleep (Duration::from_millis (500)) .await | | ^ ^ only allowed inside `async` functions and blockserror [E0277]: the trait bound `(): std::future:: Future` is not satisfied-- > src/main.rs:4:17 | 4 | fn sleepus ()-> impl std::future::Future {| ^ the trait `std::future:: Future` is not implemented for` () `| = note: the return type of a function must have a statically known size |

The first error message is straightforward: you can only use .await syntax in async functions or code blocks. We haven't touched the asynchronous code block yet, but that's what it looks like:

Async {/ / async noises intensify}

The second error message is the result of the first: the async keyword requires that the function return type be impl Future. Without this keyword, our loop result type is (), which obviously does not meet the requirement.

Wrapping the entire function body in an asynchronous code block solves the problem:

Fn sleepus ()-> impl std::future::Future {async {for i in 1.. syntax 10 {println! ("Sleepus {}", I); sleep (Duration::from_millis (500)) .await;}} 4, the role of .await syntax

Maybe we don't need all these async/.await. What happens if we remove .await from sleepus? Surprisingly, the compilation passed, albeit with a warning:

Warning: unused implementer of `std::future:: Future` that must be used-- > src/main.rs:8:13 | 8 | sleep (Duration::from_millis); ^ | = note: `# [warn (unused_must_use)] `on by default = note: futures do nothing unless you `.await` or poll them

We are generating a Fusion value but are not using it. If you look at the output of the program, you can understand what the compiler's warning means:

Interruptus 1Sleepus 1Sleepus 2Sleepus 3Sleepus 4Sleepus 5Sleepus 6Sleepus 7Sleepus 8Sleepus 9Sleepus 10Interruptus 2Interruptus 3Interruptus 4Interruptus 5

All of our Sleepus message output has no latency. The problem is that the call to sleep doesn't actually rest the current thread, it just generates a value that implements Future, and then when the promise is finally implemented, we know that there is a delay. But because we simply ignored Future, we didn't actually take advantage of latency.

To understand exactly what the .await syntax does, we then directly use the Fusion value to implement our function. First of all, never start with async blocks.

5. Rust asynchronous code without the async keyword

If we lose the async code block, it looks like this:

Fn sleepus ()-> impl std::future::Future {for i in 1.. 10 {println! ("Sleepus {}", I); sleep (Duration::from_millis (500));}}

In this way, the following error occurs in compilation:

Error [E0277]: the trait bound `(): std::future:: Future` is not satisfied-- > src/main.rs:4:17 | 4 | fn sleepus ()-> impl std::future::Future {| ^ ^ the trait `std::future:: Future` is not implemented for` () `|

The above error is because the result type of the for loop is (), which does not implement the trait of Future. One way to fix this is to add a sentence after the for loop to return the implementation type of Future. We already know that we can use this: sleep:

Fn sleepus ()-> impl std::future::Future {for i in 1.. 10 {println! ("Sleepus {}", I); sleep (Duration::from_millis (500));} sleep (Duration::from_millis (0))}

We will still see a warning message that there is an unused Future value in the for loop, but the error of the return value has been resolved. This sleep call actually does nothing, and we can replace it with a real placeholder Future:

Fn sleepus ()-> impl std::future::Future {for i in 1.. Future 10 {println! ("Sleepus {}", I); sleep (Duration::from_millis (500));} async_std::future::ready (())} 6. Implement your own Future

To get to the bottom of this, let's go one step further and not apply the ready function in the async_std library, but instead define our own structure for implementing Future. Let's call it DoNothing.

Use std::future::Future;struct DoNothing;fn sleepus ()-> impl Future {for i in 1.. Sleepus 10 {println! ("Sleepus {}", I); sleep (Duration::from_millis (500));} DoNothing}

The problem is that DoNothing does not yet provide a Future implementation. We're going to do some compiler-driven development next, and let rustc tell us how to fix the program. The first error message is:

The trait bound `DoNothing: std::future:: Future` is not satisfied

So let's make up the implementation of this trait:

Impl Future for DoNothing {}

Continue to report errors:

Error [E0046]: not all trait items implemented, missing: `Output`, `poll`-> src/main.rs:7:1 | 7 | impl Future for DoNothing {| ^ missing `Output`, `poll` in implementation | = note: `Output` from trait: `type Output; `= note: `poll` from trait: `fn (std::pin::Pin, & mut std::task::Context)

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report