diff --git a/executor/async-await.html b/executor/async-await.html index 5433600..d4f36f2 100644 --- a/executor/async-await.html +++ b/executor/async-await.html @@ -162,7 +162,7 @@
The poll
method starts a loop
because in the case that one of the states isn’t blocked, the state machine can perform multiple state transitions in a single poll
call. This reduces the number of poll
calls the executor needs to make.
Now, let’s look at how each state performs the state transition.
-When we initialize NotifyUser
, its state
is State::Unpolled
, which represents the starting state. When we poll
NotifyUser
for the first time, it calls async_fetch_user
to instantiate and store the fetch_user_fut
state machine.
When we initialize NotifyUser
, its state
is State::Unpolled
, which represents the starting state. When we poll
NotifyUser
for the first time, it calls async_fetch_user
to instantiate and store the fetch_user_fut
state machine.
It then transitions its state
to State::FetchingUser
. Note that this code block doesn’t return Poll::Pending
. This is because none of the executed code is blocking, so we can go ahead and execute the handle for the next state transition.
#![allow(unused)] fn main() { State::Unpolled => { - self.fetch_user_fut = Some(async_fetch_user(self.user_id)); - self.state = State::FetchingUser; + self.fetch_user_fut = Some(async_fetch_user(self.user_id)); + self.state = State::FetchingUser; } }
When we get to the FetchinUser
state, it poll
s the fetch_user_fut
to see if it’s ready. If it’s Pending
, we return Poll::Pending
. Otherwise, NotifyUser
can perform its next state transition. If self.user.group == 1
, it needs to create and store the fetch_user_fut
state machine and transition the state to State::SendingEmail
. Otherwise, it can transition its state to State::Ready
.
#![allow(unused)] fn main() { State::FetchingUser => { - match self.fetch_user_fut.unwrap().poll(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(user) => { - self.user = Some(user); - if self.user.group == 1 { - self.fetch_user_fut = Some(async_send_email(&self.user)); - self.state = State::SendingEmail; - } else { - self.state = State::Ready; - } - } + match self.fetch_user_fut.unwrap().poll(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(user) => { + self.user = Some(user); + if self.user.group == 1 { + self.fetch_user_fut = Some(async_send_email(&self.user)); + self.state = State::SendingEmail; + } else { + self.state = State::Ready; + } } + } } }
If the state is SendingEmail
, it polls send_email_fut
to check if it’s ready. If it is, it transitions the state to State::Ready
. Otherwise, it returns Poll::Pending
.
#![allow(unused)] fn main() { State::SendingEmail => { - match self.send_email_fut.unwrap().poll(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(()) => { - self.state = State::Ready; - } + match self.send_email_fut.unwrap().poll(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(()) => { + self.state = State::Ready; } + } } }
Finally, if the state is Ready
, NotifyUser
returns Poll::Ready(())
to indicate that the state machine is complete.
Next, it calls spawn
to create and schedule the task onto the TaskQueue
.
Next, it calls spawn
to create and schedule the task onto the TaskQueue
.
It then loops until the future
is completed. It’s super important to understand that the poll
method here doesn’t actually poll
the user-provided future. It simply poll
s the JoinHandle
, which checks if the COMPLETED
flag on the task’s state
is set.
Since the executor
is single-threaded, looping alone won’t actually progress the underlying future. Therefore, in each loop, the executor
calls the run_task_queues
method.
run_task_queues
simply loops and calls run_one_task_queue
until there are no more task
s left in the TaskQueue
.
The poll
method starts a loop
because in the case that one of the states isn’t blocked, the state machine can perform multiple state transitions in a single poll
call. This reduces the number of poll
calls the executor needs to make.
Now, let’s look at how each state performs the state transition.
-When we initialize NotifyUser
, its state
is State::Unpolled
, which represents the starting state. When we poll
NotifyUser
for the first time, it calls async_fetch_user
to instantiate and store the fetch_user_fut
state machine.
When we initialize NotifyUser
, its state
is State::Unpolled
, which represents the starting state. When we poll
NotifyUser
for the first time, it calls async_fetch_user
to instantiate and store the fetch_user_fut
state machine.
It then transitions its state
to State::FetchingUser
. Note that this code block doesn’t return Poll::Pending
. This is because none of the executed code is blocking, so we can go ahead and execute the handle for the next state transition.
#![allow(unused)] fn main() { State::Unpolled => { - self.fetch_user_fut = Some(async_fetch_user(self.user_id)); - self.state = State::FetchingUser; + self.fetch_user_fut = Some(async_fetch_user(self.user_id)); + self.state = State::FetchingUser; } }
When we get to the FetchinUser
state, it poll
s the fetch_user_fut
to see if it’s ready. If it’s Pending
, we return Poll::Pending
. Otherwise, NotifyUser
can perform its next state transition. If self.user.group == 1
, it needs to create and store the fetch_user_fut
state machine and transition the state to State::SendingEmail
. Otherwise, it can transition its state to State::Ready
.
#![allow(unused)] fn main() { State::FetchingUser => { - match self.fetch_user_fut.unwrap().poll(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(user) => { - self.user = Some(user); - if self.user.group == 1 { - self.fetch_user_fut = Some(async_send_email(&self.user)); - self.state = State::SendingEmail; - } else { - self.state = State::Ready; - } - } + match self.fetch_user_fut.unwrap().poll(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(user) => { + self.user = Some(user); + if self.user.group == 1 { + self.fetch_user_fut = Some(async_send_email(&self.user)); + self.state = State::SendingEmail; + } else { + self.state = State::Ready; + } } + } } }
If the state is SendingEmail
, it polls send_email_fut
to check if it’s ready. If it is, it transitions the state to State::Ready
. Otherwise, it returns Poll::Pending
.
#![allow(unused)] fn main() { State::SendingEmail => { - match self.send_email_fut.unwrap().poll(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(()) => { - self.state = State::Ready; - } + match self.send_email_fut.unwrap().poll(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(()) => { + self.state = State::Ready; } + } } }
Finally, if the state is Ready
, NotifyUser
returns Poll::Ready(())
to indicate that the state machine is complete.
Next, it calls spawn
to create and schedule the task onto the TaskQueue
.
Next, it calls spawn
to create and schedule the task onto the TaskQueue
.
It then loops until the future
is completed. It’s super important to understand that the poll
method here doesn’t actually poll
the user-provided future. It simply poll
s the JoinHandle
, which checks if the COMPLETED
flag on the task’s state
is set.
Since the executor
is single-threaded, looping alone won’t actually progress the underlying future. Therefore, in each loop, the executor
calls the run_task_queues
method.
run_task_queues
simply loops and calls run_one_task_queue
until there are no more task
s left in the TaskQueue
.