Can I give up on await after some time, on async Rust?

Normally on async Rust, when I want to poll a Future until it returns, I do

let s = my_function().await;

The runtime will poll it efficiently, but it will also block the current thread until it finishes.

What if I want a poll that can be canceled? Example:

pub fn play(client: Arc<Mutex<Client>>) {
    client.lock().unwrap().my_thread = std::thread::spawn(move ||{
        do_something().await;
        do_something_2().await;
        do_something_3().await;
    });
}

I might call my_client.play() and a thread could be launched. Note that my_client still owns the thread. What if I want to destruct my_client, but the thread is still going?

Is there a way to await for a result, but keep looking into a variable every 100ms to see if I should give up on await?

Note that async doesn't use threads, it uses tasks. So your code should actually use tokio::spawn instead of std::thread::spawn, and should store a tokio::task::JoinHandle. A simple way to achieve what you want is to abort the task in the destructor of Client:

impl Drop for Client {
    fn drop(&mut self) {
        self.my_task.abort();
    }
}

This uses JoinHandle::abort, which will stop the spawned future from running instantly.

I'm not sure if you're using Tokio or not, but if you are then you can wrap your do_something calls using the timeout function. Other runtimes may have equivalents to timeout.

1 Like

@huntc Note that your link is to an extremely ancient version of Tokio. (But the newest version has the method too.)

You can read more about remotely shutting down tasks here.

1 Like

You don't have an accurate mental model of how async works in Rust. It doesn't occupy threads, doesn't block execution. The blocking of .await is just an illusion created for ease of programming.

Most importantly, async code doesn't run by itself. It only makes progress when it's polled. If owner of a task stops polling it, it ceases to execute. "Running" async code can be stopped/abandoned at any .await point.

This is complete opposite of how threads work (where if you ignore thread's existence, it keeps going) or how Promises work in JS (where async code keeps going even if you don't actively wait for it).

So in your code you don't have to do anything to support giving up on it. If the user of your async fn gives up on it, your code will just stop running (.await will return from the function forever, and everything the function owned will be destroyed).

If you want to have a timeout within a function, you can wrap any async future in tokio::timeout. async spawn's JoinHandle also has ability to abort async code from the handle. You can also wrap futures in Abortable future to have such handle without need for spawn.`

To abort previous play command when it's run for the second time, you could do something like:

if let Some(join_handle) = currently_playing.take() {
    join_handle.abort();
}
currently_playing = Some(tokio::spawn(async { play().await; });

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.