Immediate execution of async function in tokio

According to my understanding of tokio, if I call await on an async function, tokio will yield to the scheduler and potentially execute other scheduled async functions before it computes the one I just awaited.
Is my understanding correct here? If yes, is there a way to tell tokio to immediately execute an async function such that it's guaranteed that it won't schedule anything else before?

...only if this async function returns Poll::Pending, i.e. if it yields to the executor itself. But it will start execution immediately, without yielding, and, if it completes in one go (i.e. returns Poll::Ready at the first poll) - your function will not yield either.

1 Like

@corvusrabus should note, however, that their task will return Poll::Pending at some point if it does basically any IO.

To Corvus, I would ask: what do you want tokio to do during the waits for IO, if and when your async call needs to do some? I can think of a few possibilities, including "nothing, wait until the IO completes," but which one do you actually want, and why?

That's a great answer and resolves exactly what I wanted to know. I don't really understand the inner workings of async code because I only access functions that actually wait for io or networking through libraries.

In my case, it's not relevant what happens after the task yields, it's only relevant that there's no interruption between calling it and the point where it yields. So in this sense, Cerber-Ursi's answer is all that I need to know. Thanks for the help.

Why would you say that? Almost any write will success immediately if it fits in current kernel buffer. And would a read handled from the kernel buffer be considered IO - most would consider reading a UDP packet IO even after it arrived?

Often you want to essentially try to see if it completes and at least you kicked it off., but even on a PENDING result you want to keep going. eg, client sends me a request that requires two results. I compute the first and kick off the send, but still have to compute the second. For domain specific reasons latency matters so want to get the first off asap. There doesn't appear to be a good way to do this in async.

You have to write a future manually to do this, but it's pretty straightforward:

A crate may have something similar ready-made; I'm not familiar with the wider async ecosystem.

pub struct TryFuture<'a, F>(Pin<&'a mut F>);

impl<'a, F> Future for TryFuture<'a, F>
where
    F: Future,
{
    type Output = Poll<F::Output>;
    fn poll(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Poll<F::Output>> {
        Poll::Ready(self.0.as_mut().poll(ctx))
    }
}

/// Example Usage

pub async fn example() {
    // artificial future that returns `Pending` n times before returning `Ready`
    let inner_fut = Yield(5);
    pin!(inner_fut);
    
    let _ = dbg!(TryFuture(inner_fut.as_mut()).await);
    
    eprintln!("Still Running");
    
    dbg!(inner_fut.await);
}

Thanks. I was thinking about this but didn't know how to shoehorn it in without abusing Ready() too much. If poll was two calls it would be a little nicer i think. I didn't think about wrapping a future around the one already there. that just opens up ideas a lot. useful.

Actually I rarely need the results of these, if ever. I get an ack back on a read channel if it went out successfully. maybe i could reparent it under another task for pending writes? it that is even possible.

That already exists as now_or_never in the futures crate.

1 Like

I still need the future to complete, just the return value isn't very relevant except for that it did complete. That is whaht the reparent would have been for to ensure completion and log erros if not.

You can call now_or_never on a Pin<&mut Future> like with the TryFuture, so inner_fut.as_mut().now_or_never() will let you await the future afterwards if it didn't complete.

1 Like

I think that winds up being similar to what I did for a POC - someone in Discord helped me put together an outer future to wrap the real future (well - they did most of the coding and I did most of the listening). Fell a little hacky since you are abusing the Ready value, but not horribly I don't think.

I'n trying to do some timing on write() to socket directly vs write through io_uring for latency to see if there is any penalty. If there isn't that will be the actual write then I wont have to worry about completing now vs later but just always return ready regardless and reap the result from the completion events at the end of the cycle.

uring is more write sympathetic anyways wtih completion systems, so was already planning on moving writes to uring anyways and keeping reads on epoll in c++ system.

I still want to see if there is a way to move a future to a task that jus reaps results, but I don't think so. (I could pass the completion handle to a separate task I guess and make a new future around it, so have to think about a little).

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.