Instead, we will require each future to tell us when it can make progress. The point of this closure is to allow the executor to not waste its time continuously checking to see if a future can make progress. This is where the wake part of the poll function comes in. This is a very basic event loop, but we can do better. If let Poll::Ready(d) = fut.poll( ||) // it expects a wake function but we are not using it yet if result of `fut.poll(.)` is `Poll::Ready(d)` then grab data as `d` We can think about how we can implement an executor similar to our first asynchronous function idea: We could iterate over each future and poll it to check if it is ready and if it is not ready, just poll it again. In this article, we will only be talking about how an event loop driven executor works using a single thread. How to run it, whether that be single-threaded or multi-threaded, with priorities, and so on, is left very open-ended as far as Rust is concerned. Defining an executorĪn executor is what takes one or multiple futures and runs them. Note, a task might have multiple things to wait on, and thus poll may need to return Poll::Pending multiple times before it can eventually return Poll::Ready(data). So, as soon it knows it can’t make progress, it should return Poll::Pending (like when has_data_to_read() is false). our toy socket is done waiting), then we can return that data as Poll::Ready(data). The return type for poll() is of type Poll, which is an algebraic data type that can be Poll::Ready(data) or Poll::Pending as shown by the enum. When we call poll on a future we are allowing progress to be made on that future. This is important because polling is where the magic in asynchronous programming happens.īefore we talk about how we will implement the poll function, lets look into how we use it. This trait requires anything that wants to implement Future to have a definition for poll. To make it act more like a real socket we’ll have a few functions that we can use to interface with it.įn poll( & mut self, wake: fn()) -> Poll In short, all it does is wait 2 seconds before the data is available and then it sets the data to be a single random i32 (an int in C). Let’s think about a very simple fake socket operation. While this is very important for Rust to do, it’s also very complicated and best left for more extended readings which I’ve also noted towards the end of the article. This article does not come close to covering the more specialized workings of futures in Rust, especially when explaining how Rust can guarantee memory safety throughout all of this. I chose to talk about Rust specifically because Rust is a systems-level language meaning that, even for a high-level idea, there is almost no hidden magic that is happening behind the scenes. This article will not talk about how to use async/await in the traditional sense however, knowing how the underlying concepts work is very useful for programming async/await. However, most of the time when programming asynchronous code, you will only ever have to use async/await because everything low level is already implemented. This article will be talking a lot about the implementation of futures in Rust. This allows our program to always be making progress, which is the name of the game. This is a perfect moment to hand over control to something else so that it can start to make progress. Asynchronous programming in RustĪsynchronous programming is used a lot for IO because there are many times where you have to wait for something to happen (such as reading from a socket or a file) during which no progress can be made. In Rust, it is accomplished using a high-level idea called a Future. Asynchronous programming is a method of programming that can allow multiple different things to be run concurrently (or in parallel).
0 Comments
Leave a Reply. |
AuthorWrite something about yourself. No need to be fancy, just an overview. ArchivesCategories |